home *** CD-ROM | disk | FTP | other *** search
/ Collection of Tools & Utilities / Collection of Tools and Utilities.iso / keyb / trecal12.zip / RECALL.ASM < prev    next >
Assembly Source File  |  1991-11-16  |  108KB  |  2,499 lines

  1. ;--------------------------------------------------------------------------;
  2. ;  Program:    Recall  .Asm                                                ;
  3. ;  Purpose:    Commandline editor and history TSR.                         ;
  4. ;  Notes:      Compiles under TURBO Assembler, v2.0. Requires DOS v2.xx    ;
  5. ;                 or higher. Editing keys are coded as PC extended scan    ;
  6. ;                 codes; otherwise, this uses only DOS calls.              ;
  7. ;              The overall design is derived from RDE (aka, Rainbow DOS    ;
  8. ;                 Editor) by Joe Kneidel. The methods used to install and  ;
  9. ;                 uninstall this TSR are from _MS-DOS Developer's Guide_,  ;
  10. ;                 by Angermayer and Jaeger.                                ;
  11. ;  Status:     Released into the >>>public domain<<<. Enjoy! If you use    ;
  12. ;                 it, let me know what you think. You don't have to send   ;
  13. ;                 any money, just comments and suggestions.                ;
  14. ;  Updates:    24-Oct-90, v1.0a, GAT                                       ;
  15. ;                 - initial version                                        ;
  16. ;              28-Oct-90, v1.0b, GAT                                       ;
  17. ;                 - renamed get_LineFromUser to get_CmdLine and            ;
  18. ;                   add_LineToBuffer to store_CmdInBuf.                    ;
  19. ;                 - made sure to zero out CH in add_LineToBuffer.          ;
  20. ;                 - excluded CR from byte count in get_CmdLine.            ;
  21. ;                 - kept track of CurCmd rather than PrevCmd/NextCmd and   ;
  22. ;                   moved checks on command from recall_CmdFromBuf to      ;
  23. ;                   mov_pcmd and mov_ncmd.                                 ;
  24. ;                 - specified command table as an array of structures and  ;
  25. ;                   revised ways it was accessed in get_CmdLine.           ;
  26. ;                 - rearranged various procedures.                         ;
  27. ;                 - spruced up comments.                                   ;
  28. ;              31-Oct-90, v1.1a, GAT                                       ;
  29. ;                 - removed notices about preliminary notices.             ;
  30. ;                 - cleanup up help message a bit.                         ;
  31. ;                 - avoided use of LABELs.                                 ;
  32. ;                 - added list_CmdLines to list recall buffer contents.    ;
  33. ;              10-Nov-91, v1.2a, GAT                                       ;
  34. ;                 - caught and fixed bug involving DOS input redirection   ;
  35. ;                   which caused 0Ah characters to remain in commandline.  ;
  36. ;                 - revised include file names.                            ;
  37. ;                 - added pseudo-environment so program name will show up  ;
  38. ;                   with things like PMAP, MANIFEST, and MEM.              ;
  39. ;                 - uses INT 2D as per Ralf Brown's Alternate Multiplex    ;
  40. ;                   Interrupt proposal.                                    ;
  41. ;                 - shares interrupts as per IBM's Interrupt Sharing       ;
  42. ;                   Protocol.                                              ;
  43. ;              16-Nov-91, GAT                                              ;
  44. ;                 - made minor changes in return values from the Int 2d    ;
  45. ;                   handler to track Ralf's proposal.                      ;
  46. ;--------------------------------------------------------------------------;
  47.  
  48. ;--------------------------------------------------------------------------;
  49. ;  Author:     George A. Theall                                            ;
  50. ;  Phone:      +1 215 662 0558                                             ;
  51. ;  SnailMail:  TifaWARE                                                    ;
  52. ;              506 South 41st St., #3M                                     ;
  53. ;              Philadelphia, PA.  19104   USA                              ;
  54. ;  E-Mail:     theall@gdalsrv.sas.upenn.edu (Internet)                     ;
  55. ;--------------------------------------------------------------------------;
  56.  
  57. %NEWPAGE
  58. ;--------------------------------------------------------------------------;
  59. ;                          D I R E C T I V E S                             ;
  60. ;--------------------------------------------------------------------------;
  61. DOSSEG
  62. MODEL     tiny
  63.  
  64. IDEAL
  65. LOCALS
  66. JUMPS
  67.  
  68. ;
  69. ; This section comes from Misc.Inc.
  70. ;
  71. @16BIT              EQU       (@CPU AND 8) EQ 0
  72. @32BIT              EQU       (@CPU AND 8)
  73. MACRO    ZERO     RegList                    ;; Zeros registers
  74.    IRP      Reg, <RegList>
  75.          xor      Reg, Reg
  76.    ENDM
  77. ENDM
  78.  
  79. ;
  80. ; This section comes from DOS.Inc.
  81. ;
  82. BELL                EQU       7
  83. BS                  EQU       8
  84. TAB                 EQU       9
  85. CR                  EQU       13
  86. LF                  EQU       10
  87. ESCAPE              EQU       27             ; nb: ESC is a TASM keyword
  88. SPACE               EQU       ' '
  89. KEY_F1              EQU       3bh
  90. KEY_F2              EQU       3ch
  91. KEY_F3              EQU       3dh
  92. KEY_F4              EQU       3eh
  93. KEY_F5              EQU       3fh
  94. KEY_F6              EQU       40h
  95. KEY_F7              EQU       41h
  96. KEY_F8              EQU       42h
  97. KEY_F9              EQU       43h
  98. KEY_F10             EQU       44h
  99. KEY_HOME            EQU       47h
  100. KEY_UP              EQU       48h
  101. KEY_PGUP            EQU       49h
  102. KEY_LEFT            EQU       4bh
  103. KEY_RIGHT           EQU       4dh
  104. KEY_END             EQU       4fh
  105. KEY_DOWN            EQU       50h
  106. KEY_PGDN            EQU       51h
  107. KEY_INS             EQU       52h
  108. KEY_DEL             EQU       53h
  109. KEY_C_F1            EQU       5eh
  110. KEY_C_F2            EQU       5fh
  111. KEY_C_F3            EQU       60h
  112. KEY_C_F4            EQU       61h
  113. KEY_C_F5            EQU       62h
  114. KEY_C_F6            EQU       63h
  115. KEY_C_F7            EQU       64h
  116. KEY_C_F8            EQU       65h
  117. KEY_C_F9            EQU       66h
  118. KEY_C_F10           EQU       67h
  119. KEY_C_LEFT          EQU       73h
  120. KEY_C_RIGHT         EQU       74h
  121. KEY_C_END           EQU       75h
  122. KEY_C_PGDN          EQU       76h
  123. KEY_C_HOME          EQU       77h
  124. KEY_C_PGUP          EQU       84h
  125. KEY_F11             EQU       85h
  126. KEY_F12             EQU       86h
  127. KEY_C_F11           EQU       89h
  128. KEY_C_F12           EQU       8ah
  129. DOS                 EQU       21h            ; main MSDOS interrupt
  130. STDIN               EQU       0              ; standard input
  131. STDOUT              EQU       1              ; standard output
  132. STDERR              EQU       2              ; error output
  133. STDAUX              EQU       3              ; COM port
  134. STDPRN              EQU       4              ; printer
  135. TSRMAGIC            EQU       424bh          ; magic number
  136. STRUC     ISR
  137.           Entry     DW        10EBh          ; short jump ahead 16 bytes
  138.           OldISR    DD        ?              ; next ISR in chain
  139.           Sig       DW        TSRMAGIC       ; magic number
  140.           EOIFlag   DB        ?              ; 0 (80) if soft(hard)ware int
  141.           Reset     DW        ?              ; short jump to hardware reset
  142.           Reserved  DB        7 dup (0)
  143. ENDS
  144. STRUC     ISRHOOK
  145.           Vector    DB        ?              ; vector hooked
  146.           Entry     DW        ?              ; offset of TSR entry point
  147. ENDS
  148. STRUC     TSRSIG
  149.           Company   DB        8 dup (" ")    ; blank-padded company name
  150.           Product   DB        8 dup (" ")    ; blank-padded product name
  151.           Desc      DB        64 dup (0)     ; ASCIIZ product description
  152. ENDS
  153. GLOBAL at : PROC
  154. GLOBAL errmsg : PROC
  155.    GLOBAL ProgName : BYTE                    ; needed for errmsg()
  156.    GLOBAL EOL : BYTE                         ; ditto
  157. GLOBAL fgetc : PROC
  158. GLOBAL fputc : PROC
  159. GLOBAL fputs : PROC
  160. GLOBAL getchar : PROC
  161. GLOBAL getdate : PROC
  162. GLOBAL getswtch : PROC
  163. GLOBAL gettime : PROC
  164. GLOBAL getvdos : PROC
  165. GLOBAL getvect : PROC
  166. GLOBAL isatty : PROC
  167. GLOBAL kbhit : PROC
  168. GLOBAL pause : PROC
  169. GLOBAL putchar : PROC
  170. GLOBAL setvect : PROC
  171. GLOBAL sleep : PROC
  172. GLOBAL find_NextISR : PROC
  173. GLOBAL find_PrevISR : PROC
  174. GLOBAL hook_ISR : PROC
  175. GLOBAL unhook_ISR : PROC
  176. GLOBAL free_Env : PROC
  177. GLOBAL fake_Env : PROC
  178. GLOBAL check_ifInstalled : PROC
  179. GLOBAL install_TSR : PROC
  180. GLOBAL remove_TSR : PROC
  181.  
  182. ;
  183. ; This section comes from Math.Inc.
  184. ;
  185. GLOBAL atoi : PROC
  186. GLOBAL atou : PROC
  187. GLOBAL utoa : PROC
  188.  
  189. ;
  190. ; This section comes from String.Inc.
  191. ;
  192. EOS                 EQU       0              ; terminates strings
  193. GLOBAL isdigit : PROC
  194. GLOBAL islower : PROC
  195. GLOBAL isupper : PROC
  196. GLOBAL iswhite : PROC
  197. GLOBAL memcmp : PROC
  198. GLOBAL strchr : PROC
  199. GLOBAL strcmp : PROC
  200. GLOBAL strlen : PROC
  201. GLOBAL tolower : PROC
  202. GLOBAL toupper : PROC
  203.  
  204.  
  205. VERSION   equ       '1.2a'                   ; current version of program
  206.                                              ; nb: change TSR_Ver too!
  207.  
  208. ; BUFSIZE specifies size of the recall buffer for collecting commandlines.
  209. ; Values of 255 or below are risky because that's the maximum buffer size
  210. ; for subfunction 10 of Int 21h and my code in add_LineToBuffer does not 
  211. ; make sure commandlines will fit. I foresee no problems, however, with 
  212. ; larger values up to about 60K.
  213. BUFSIZE   equ       1024                     ; >>>CHANGE AT YOUR RISK<<<
  214. ERRH      equ       1                        ; errorlevel if help given
  215. ERRINS    equ       10                       ; errorlevel if install failed
  216. ERRUNI    equ       20                       ; errorlevel if uninstall failed
  217. ERRNYI    equ       25                       ; errorlevel if not yet installed
  218. OFF       equ       0
  219. ON        equ       1
  220.  
  221.  
  222. %NEWPAGE
  223. ;--------------------------------------------------------------------------;
  224. ;                        C O D E    S E G M E N T                          ;
  225. ;--------------------------------------------------------------------------;
  226. CODESEG
  227.  
  228. ORG       0                                  ; address of code segment start
  229. SegStart  DB        ?                        ;    used in when installing
  230.  
  231. ORG       80h                                ; address of commandline
  232. CmdLen    DB        ?
  233. CmdLine   DB        127 DUP (?)
  234.  
  235. ORG       100h                               ; start of .COM file
  236. STARTUPCODE
  237.           jmp       main
  238.  
  239.  
  240. %NEWPAGE
  241. ;--------------------------------------------------------------------------;
  242. ;                        R E S I D E N T   D A T A                         ;
  243. ;--------------------------------------------------------------------------;
  244. TSR_Sig   TSRSIG    <'TifaWARE', 'RECALL  ',\
  245.                     'commandline editor and history TSR'>
  246. TSR_Ver   DW        (2 SHL 8) + 1            ; (minor shl 8) + major
  247. MPlex     DB        ?                        ; multiplex ID
  248. HookTbl   ISRHOOK   <21h, do_Int21>
  249.           ISRHOOK   <2dh, do_Int2D>          ; 2d must be last!!!
  250.  
  251. OldAX     DW        ?                        ; value of AX register when my
  252.                                              ;    handler is first called
  253. OldStack  DD        ?                        ; address of caller's stack
  254. CurCmd    DW        0                        ; pointer to current command
  255.                                              ;    in recall buffer
  256. InsMode   DB        ON                       ; InsertMode toggle flag
  257.  
  258. STRUC     CMD                                ; structure for editing cmd
  259.           Key       DB        ?              ;    extended code for key
  260.           Function  DW        ?              ;    address of editing function
  261. ENDS
  262. CmdTbl    CMD       <KEY_LEFT,     OFFSET mov_lchar>
  263.           CMD       <KEY_RIGHT,    OFFSET mov_rchar>
  264.           CMD       <KEY_PGUP,     OFFSET mov_lword>
  265.           CMD       <KEY_PGDN,     OFFSET mov_rword>
  266.           CMD       <KEY_HOME,     OFFSET mov_bol>
  267.           CMD       <KEY_END,      OFFSET mov_eol>
  268.           CMD       <KEY_UP,       OFFSET mov_pcmd>
  269.           CMD       <KEY_DOWN,     OFFSET mov_ncmd>
  270.           CMD       <BS,           OFFSET del_lchar>
  271.           CMD       <KEY_C_LEFT,   OFFSET del_lchar>
  272.           CMD       <KEY_DEL,      OFFSET del_rchar>
  273.           CMD       <KEY_C_RIGHT,  OFFSET del_rchar>
  274.           CMD       <KEY_C_PGUP,   OFFSET del_lword>
  275.           CMD       <KEY_C_PGDN,   OFFSET del_rword>
  276.           CMD       <KEY_C_HOME,   OFFSET del_bol>
  277.           CMD       <KEY_C_END,    OFFSET del_eol>
  278.           CMD       <ESCAPE,       OFFSET del_line>
  279.           CMD       <KEY_C_F9,     OFFSET del_buf>
  280.           CMD       <KEY_INS,      OFFSET toggle_InsMode>
  281.           CMD       <0,            OFFSET ring_Bell>   ; >>>must be last<<<
  282.  
  283.  
  284. %NEWPAGE
  285. ;--------------------------------------------------------------------------;
  286. ;                          L O C A L   S T A C K                           ;
  287. ;--------------------------------------------------------------------------;
  288.           DB        16 dup("STACK   ")       ; 128 bytes for local stack
  289. StackTop  =         $
  290.  
  291.  
  292. %NEWPAGE
  293. ;--------------------------------------------------------------------------;
  294. ;                        R E S I D E N T   C O D E                         ;
  295. ;--------------------------------------------------------------------------;
  296. ;----  is_CharWhite  ------------------------------------------------------;
  297. ;  Purpose:    Tests if character is either a blank or a tab.              ;
  298. ;  Notes:      none                                                        ;
  299. ;  Entry:      AL = character to be tested.                                ;
  300. ;  Exit:       Zero flag set if true, cleared otherwise.                   ;
  301. ;  Calls:      none                                                        ;
  302. ;  Changes:    flags                                                       ;
  303. ;--------------------------------------------------------------------------;
  304. PROC is_CharWhite
  305.  
  306.           cmp       al, SPACE                ; if == SPACE then zf = 1
  307.           jz        SHORT @@Fin
  308.           cmp       al, TAB                  ; if == TAB then zf = 1
  309. @@Fin:
  310.           ret
  311. ENDP is_CharWhite
  312.  
  313.  
  314. ;----  get_KeyNoEcho  -----------------------------------------------------;
  315. ;  Purpose:    Reads key from STDIN, waiting as necessary.                 ;
  316. ;  Notes:      Allows DESQview to operate efficiently if task inactive.    ;
  317. ;              Ctrl-C and Ctrl-Break generate Int 23h.                     ;
  318. ;  Entry:      n/a                                                         ;
  319. ;  Exit:       AL = character (0 => extended code available next).         ;
  320. ;  Calls:      none                                                        ;
  321. ;  Changes:    AX                                                          ;
  322. ;--------------------------------------------------------------------------;
  323. PROC get_KeyNoEcho
  324.  
  325.           mov       ah, 8
  326.           int       DOS
  327.           ret
  328. ENDP get_KeyNoEcho
  329.  
  330.  
  331. ;----  ring_Bell  ---------------------------------------------------------;
  332. ;  Purpose:    Rings the console bell as a warning to user.                ;
  333. ;  Notes:      none                                                        ;
  334. ;  Entry:      n/a                                                         ;
  335. ;  Exit:       n/a                                                         ;
  336. ;  Calls:      none                                                        ;
  337. ;  Changes:    none                                                        ;
  338. ;--------------------------------------------------------------------------;
  339. PROC ring_Bell
  340.  
  341.           push      ax dx
  342.           mov       ah, 2
  343.           mov       dl, BELL
  344.           int       DOS
  345.           pop       dx ax
  346.           ret
  347. ENDP ring_Bell
  348.  
  349.  
  350. ;----  display_Char  ------------------------------------------------------;
  351. ;  Purpose:    Displays character on STDOUT and advances to next char.     ;
  352. ;  Notes:      Do *not* call this procedure to display backspaces; use     ;
  353. ;                   backup_Cursor instead. Though they'd be displayed      ;
  354. ;                   properly, BX would be *incremented* here.              ;
  355. ;              Does *not* adjust CH or CL.                                 ;
  356. ;  Entry:      AL = character to display,                                  ;
  357. ;              BX = pointer to current position in commandline.            ;
  358. ;  Exit:       BX++                                                        ;
  359. ;  Calls:      none                                                        ;
  360. ;  Changes:    BX                                                          ;
  361. ;--------------------------------------------------------------------------;
  362. PROC display_Char
  363.  
  364.           push      ax dx
  365.           mov       dl, al
  366.           mov       ah, 2
  367.           int       DOS
  368.           inc       bx                       ; move to next char on cmdline
  369.           pop       dx ax
  370.           ret
  371. ENDP display_Char
  372.  
  373.  
  374. ;----  advance_Cursor  ----------------------------------------------------;
  375. ;  Purpose:    Moves the cursor forwards on the screen.                    ;
  376. ;  Notes:      none                                                        ;
  377. ;  Entry:      BX = pointer to current position in commandline,            ;
  378. ;              CH = # of bytes to end of line,                             ;
  379. ;              SI = # of bytes to advance.                                 ;
  380. ;  Exit:       BX += SI,                                                   ;
  381. ;              CH -= SI.                                                   ;
  382. ;  Calls:      display_Char                                                ;
  383. ;  Changes:    AX, CH,                                                     ;
  384. ;              BX (display_Char)                                           ;
  385. ;--------------------------------------------------------------------------;
  386. PROC advance_Cursor
  387.  
  388.           or        si, si                   ; anything to skip over?
  389.           jz        SHORT @@Fin
  390.  
  391. ; Adjust CH now. (This could be left until later - no big deal.)
  392.           mov       al, ch
  393.           ZERO      ah
  394.           sub       ax, si
  395.           mov       ch, al                   ; CH -= SI
  396.  
  397. ; Display SI characters on commandline.
  398.           push      cx
  399.           mov       cx, si
  400. @@NextChar:
  401.           mov       al, [bx]
  402.           call      display_Char             ; nb: increments BX too
  403.           loop      SHORT @@NextChar
  404.           pop       cx
  405.  
  406. @@Fin:
  407.           ret
  408. ENDP advance_Cursor
  409.  
  410.  
  411. ;----  backup_Cursor  -----------------------------------------------------;
  412. ;  Purpose:    Moves the cursor backwards on the screen.                   ;
  413. ;  Notes:      Does *not* handle properly line-wrapping yet.               ;
  414. ;  Entry:      BX = pointer to current position in commandline,            ;
  415. ;              CH = # of bytes to end of line,                             ;
  416. ;              SI = # of bytes to back up.                                 ;
  417. ;  Exit:       BX -= SI,                                                   ;
  418. ;              CH += SI.                                                   ;
  419. ;  Calls:      none                                                        ;
  420. ;  Changes:    AX, BX, CH                                                  ;
  421. ;--------------------------------------------------------------------------;
  422. PROC backup_Cursor
  423.  
  424.           or        si, si                   ; anything to skip over?
  425.           jz        SHORT @@Fin
  426.  
  427. ; Adjust BX and CH now. (This could be left until later - no big deal.)
  428.           sub       bx, si                   ; BX -= SI
  429.           mov       al, ch
  430.           ZERO      ah
  431.           add       ax, si
  432.           mov       ch, al                   ; CH += SI
  433.  
  434. ; Back up cursor by displaying non-destructive backspaces.
  435.           push      cx dx
  436.           mov       ah, 2
  437.           mov       cx, si
  438.           mov       dl, BS
  439. @@PrevChar:
  440.           int       DOS
  441.           loop      SHORT @@PrevChar
  442.           pop       dx cx
  443.  
  444. @@Fin:
  445.           ret
  446. ENDP backup_Cursor
  447.  
  448.  
  449. ;----  delete_Chars  ------------------------------------------------------;
  450. ;  Purpose:    Deletes characters from commandline.                        ;
  451. ;  Notes:      No checks are done on SI's validity; ie, caller should      ;
  452. ;                   ensure there are enough characters on line to delete.  ;
  453. ;  Entry:      BX = pointer to current position in commandline,            ;
  454. ;              CH = # of bytes to end of line,                             ;
  455. ;              CL = # of bytes left in commandline,                        ;
  456. ;              SI = # of characters to delete.                             ;
  457. ;  Exit:       CH -= SI,                                                   ;
  458. ;              CL += SI.                                                   ;
  459. ;  Calls:      display_Char, backup_Cursor                                 ;
  460. ;  Changes:    CH, CL, SI,                                                 ;
  461. ;              AX (backup_Cursor)                                          ;
  462. ;--------------------------------------------------------------------------;
  463. PROC delete_Chars
  464.  
  465.           or        si, si                   ; anything to delete?
  466.           jz        SHORT @@Fin
  467.  
  468. ; Adjust CH and CL now while I have SI handy. At the same time
  469. ; I am also computing the number of characters to shift.
  470.           mov       al, cl
  471.           ZERO      ah
  472.           add       ax, si
  473.           mov       cl, al                   ; CL += SI
  474.           mov       al, ch
  475.           sub       ax, si
  476.           mov       ch, al                   ; CH -= SI
  477.           push      cx                       ; final values of CH and CL
  478.           push      ax                       ; used to back up cursor
  479.  
  480. ; Shift CH - SI characters remaining on line to the left by SI characters.
  481.           mov       cx, ax                   ; CX = CH - SI
  482.           jcxz      SHORT @@Coverup          ; skip if deleting to eol
  483. @@NextChar:
  484.           mov       al, [bx+si]
  485.           mov       [bx], al
  486.           call      display_Char             ; nb: increments BX too
  487.           loop      SHORT @@NextChar
  488.  
  489. ; Display spaces to overwrite chars remaining on line.
  490. @@CoverUp:
  491.           mov       al, SPACE
  492.           mov       cx, si
  493. @@NextBlank:
  494.           call      display_Char             ; nb: increments BX too
  495.           loop      SHORT @@NextBlank
  496.  
  497. ; Back up cursor to its location on invocation.
  498.           pop       ax                       ; CH - SI
  499.           add       si, ax                   ; SI += (CH - SI)
  500.           call      backup_Cursor            ; nb: decrements BX too
  501.           pop       cx
  502.  
  503. ; NB: BX will be incremented (CH - SI) + SI times by display_Char and
  504. ; decremented CH times by backup_Cursor so overall it won't change.
  505. @@Fin:
  506.           ret
  507. ENDP delete_Chars
  508.  
  509.  
  510. ;----  add_CharToLine  ----------------------------------------------------;
  511. ;  Purpose:    Adds a character to commandline buffer.                     ;
  512. ;  Notes:      Checks to see if buffer would overflow first.               ;
  513. ;  Entry:      AL = character to add,                                      ;
  514. ;              BX = pointer to current position in line,                   ;
  515. ;              CH = number of characters until end of line,                ;
  516. ;              CL = number of bytes left in line.                          ;
  517. ;  Exit:       BX and CX changed as appropriate.                           ;
  518. ;  Calls:      display_Char, backup_Cursor, ring_Bell                      ;
  519. ;  Changes:    AX, BX, CX                                                  ;
  520. ;--------------------------------------------------------------------------;
  521. PROC add_CharToLine
  522.  
  523. ; Check for space unless insert mode is OFF *and* not at eol.
  524.           cmp       [cs:InsMode], ON
  525.           je        SHORT @@CheckForSpace
  526.           or        ch, ch
  527.           jz        SHORT @@CheckForSpace
  528.  
  529. ; Overwrite existing character while in not at eol.
  530.           mov       [bx], al
  531.           call      display_Char             ; nb: increments BX too
  532.           dec       ch
  533.           jmp       SHORT @@Fin
  534.  
  535. @@CheckForSpace:
  536.           or        cl, cl
  537.           jz        SHORT @@Abort
  538.           or        ch, ch
  539.           jnz       SHORT @@AddWithShift
  540.  
  541. ; At end of line.
  542.           mov       [bx], al
  543.           call      display_Char             ; nb: increments BX too
  544.           dec       cl
  545.           jmp       SHORT @@Fin
  546.  
  547. ; Add character and shift everything to right over by 1 position.
  548. @@AddWithShift:
  549.           push      cx dx
  550.           mov       cl, ch                   ; CH = chars to eol
  551.           ZERO      ch
  552.           mov       si, cx                   ; save for backing up
  553.           inc       cl                       ; but add 1 to display new char
  554. @@NextChar:
  555.           mov       dl, [bx]                 ; use DL as temporary storage
  556.           mov       [bx], al
  557.           call      display_Char             ; nb: increments BX too
  558.           mov       al, dl                   ; recover previous char
  559.           loop      SHORT @@NextChar
  560.           call      backup_Cursor            ; nb: decrements BX too
  561.           pop       dx cx
  562.           dec       cl
  563.           jmp       SHORT @@Fin
  564.  
  565. @@Abort:
  566.           call      ring_Bell                ; if out of space
  567.  
  568. @@Fin:
  569.           ret
  570. ENDP add_CharToLine
  571.  
  572.  
  573. ;----  find_StartofPrevWord  ----------------------------------------------;
  574. ;  Purpose:    Locates start of previous word in commandline.              ;
  575. ;  Notes:      "Words" are delineated by blanks and/or start/finish of     ;
  576. ;                   the commandline.                                       ;
  577. ;  Entry:      BX = pointer to current position in commandline.            ;
  578. ;  Exit:       SI = # of characters from BX to start of previous word.     ;
  579. ;  Calls:      is_CharWhite                                                ;
  580. ;  Changes:    AX, SI                                                      ;
  581. ;--------------------------------------------------------------------------;
  582. PROC find_StartofPrevWord
  583.  
  584.           push      bx dx
  585.           inc       dx
  586.           inc       dx                       ; DX now points to bol
  587.           mov       si, bx                   ; SI = current position
  588.  
  589. ; Skip over any whitespace. Note: don't bother with 1st character -
  590. ; think of how it should behave if positioned at start of a word.
  591. @@SkipWhite:
  592.           dec       bx
  593.           cmp       bx, dx
  594.           jb        SHORT @@Fin              ; done if BX < bol
  595.           mov       al, [bx]
  596.           call      is_CharWhite
  597.           jz        SHORT @@SkipWhite
  598.  
  599. ; Next skip over non-blanks until the start of the word.
  600. @@SkipWord:
  601.           dec       bx
  602.           cmp       bx, dx
  603.           jb        SHORT @@Fin              ; done if BX < bol
  604.           mov       al, [bx]
  605.           call      is_CharWhite
  606.           jnz       SHORT @@SkipWord
  607.  
  608. ; Finally compute how many characters must be skipped.
  609. @@Fin:
  610.           inc       bx                       ; backed up 1 too many
  611.           sub       si, bx
  612.           pop       dx bx
  613.           ret
  614. ENDP find_StartofPrevWord
  615.  
  616.  
  617. ;----  find_StartofNextWord  ----------------------------------------------;
  618. ;  Purpose:    Locates start of next word in commandline.                  ;
  619. ;  Notes:      "Words" are delineated by blanks and/or start/finish of     ;
  620. ;                   the commandline.                                       ;
  621. ;  Entry:      BX = pointer to current position in commandline.            ;
  622. ;  Exit:       SI = # of characters from BX to start of next word.         ;
  623. ;  Calls:      is_CharWhite                                                ;
  624. ;  Changes:    AX, SI                                                      ;
  625. ;--------------------------------------------------------------------------;
  626. PROC find_StartofNextWord
  627.  
  628.           push      bx dx
  629.           mov       dx, bx
  630.           mov       al, ch
  631.           ZERO      ah
  632.           add       dx, ax                   ; DX now points to eol
  633.  
  634. ; Skip over any existing word. Note: unlike find_StartofPrevWord, here
  635. ; we do not want to initially skip ahead - imagine if cursor were at
  636. ; a blank before the start of a word.
  637. @@SkipWord:
  638.           cmp       bx, dx
  639.           je        SHORT @@Fin              ; done if BX = eol
  640.           mov       al, [bx]
  641.           inc       bx
  642.           call      is_CharWhite
  643.           jnz       SHORT @@SkipWord
  644.  
  645. ; Next skip over whitespace until the start of the word.
  646. @@SkipWhite:
  647.           cmp       bx, dx
  648.           je        SHORT @@Fin              ; done if BX = eol
  649.           mov       al, [bx]
  650.           inc       bx
  651.           call      is_CharWhite
  652.           jz        SHORT @@SkipWhite
  653.           dec       bx                       ; point back to white space
  654.  
  655. ; Finally compute how many characters must be skipped.
  656. @@Fin:
  657.           mov       si, bx                   ; where we are now
  658.           pop       dx bx
  659.           sub       si, bx                   ; less where we started from
  660.           ret
  661. ENDP find_StartofNextWord
  662.  
  663.  
  664. ;----  recall_CmdFromBuf  -------------------------------------------------;
  665. ;  Purpose:    Replaces current with a commandline from buffer.            ;
  666. ;  Notes:      Does *not* check SI's validity.                             ;
  667. ;  Entry:      BX = pointer to current position in commandline,            ;
  668. ;              CH = # of bytes to end of line,                             ;
  669. ;              CL = # of bytes left in commandline,                        ;
  670. ;              SI = pointer to command in recall buffer.                   ;
  671. ;  Entry:      BX = pointer to end of new commandline,                     ;
  672. ;              CH = 0,                                                     ;
  673. ;              CL = # of bytes left in new commandline.                    ;
  674. ;  Calls:      del_line, display_Char, ring_Bell                           ;
  675. ;  Changes:    AL, BX, CH, CL, SI                                          ;
  676. ;--------------------------------------------------------------------------;
  677. PROC recall_CmdFromBuf
  678.  
  679. ; Clear current commandline and display new one.
  680.           push      si                       ; since del_line zaps SI
  681.           call      del_line                 ; changes CH and CL such that CX
  682.                                              ;   == max # of chars in buffer
  683.           pop       si
  684. @@NextChar:
  685.           mov       al, [cs:si]
  686.           cmp       al, CR
  687.           je        SHORT @@Fin
  688.           inc       si
  689.           mov       [bx], al
  690.           call      display_Char             ; nb: increments BX too
  691.           loop      SHORT @@NextChar         ; continue as long as CX > 0
  692.           cmp       [BYTE cs:si], CR         ; did loop end prematurely?
  693.           je        SHORT @@Fin              ;   no
  694.           call      ring_Bell                ;   yes, warn user
  695.  
  696. @@Fin:
  697.           ret
  698. ENDP recall_CmdFromBuf
  699.  
  700.  
  701. ;----  mov_lchar  ---------------------------------------------------------;
  702. ;  Purpose:    Moves cursor left in the commandline.                       ;
  703. ;  Notes:      none                                                        ;
  704. ;  Entry:      BX = pointer to current position in commandline,            ;
  705. ;              CH = # of bytes to end of line.                             ;
  706. ;  Exit:       BX--,                                                       ;
  707. ;              CH++,                                                       ;
  708. ;              SI = 1 (or 0 if already at bol).                            ;
  709. ;  Calls:      backup_Cursor                                               ;
  710. ;  Changes:    SI,                                                         ;
  711. ;              BX, CH (backup_Cursor)                                      ;
  712. ;--------------------------------------------------------------------------;
  713. PROC mov_lchar
  714.  
  715.           mov       si, bx
  716.           sub       si, dx
  717.           cmp       si, 2
  718.           ja        SHORT @@MoveIt           ; at bol if BX - DX <= 2
  719.           ZERO      si
  720.           jmp       SHORT @@Fin
  721.  
  722. @@MoveIt:
  723.           mov       si, 1                    ; move 1 character
  724.           call      backup_Cursor            ; nb: decrements BX too
  725.  
  726. @@Fin:
  727.           ret
  728. ENDP mov_lchar
  729.  
  730.  
  731. ;----  mov_rchar  ---------------------------------------------------------;
  732. ;  Purpose:    Moves cursor right in the commandline.                      ;
  733. ;  Notes:      none                                                        ;
  734. ;  Entry:      BX = pointer to current position in commandline,            ;
  735. ;              CH = # of bytes to end of line.                             ;
  736. ;  Exit:       BX++,                                                       ;
  737. ;              CH--,                                                       ;
  738. ;              SI = 1 (or 0 if already at eol).                            ;
  739. ;  Calls:      advance_Cursor                                              ;
  740. ;  Changes:    SI,                                                         ;
  741. ;              AX, BX, CH (advance_Cursor)                                 ;
  742. ;--------------------------------------------------------------------------;
  743. PROC mov_rchar
  744.  
  745.           ZERO      si                       ; set SI = 0 first
  746.           or        ch, ch
  747.           jz        SHORT @@Fin              ; abort if CH = 0
  748.           inc       si                       ; move 1 character
  749.           call      advance_Cursor           ; nb: increments BX
  750.  
  751. @@Fin:
  752.           ret
  753. ENDP mov_rchar
  754.  
  755.  
  756. ;----  mov_lword  ---------------------------------------------------------;
  757. ;  Purpose:    Moves cursor to start of previous word.                     ;
  758. ;  Notes:      none                                                        ;
  759. ;  Entry:      BX = pointer to current position in commandline,            ;
  760. ;              CH = # of bytes to end of line.                             ;
  761. ;  Exit:       BX and CH adjusted as appropriate.                          ;
  762. ;  Calls:      find_StartofPrevWord, backup_Cursor                         ;
  763. ;  Changes:    SI, (find_StartofPrevWord)                                  ;
  764. ;              AX, BX, CH (backup_Cursor)                                  ;
  765. ;--------------------------------------------------------------------------;
  766. PROC mov_lword
  767.  
  768.           call      find_StartofPrevWord
  769.           call      backup_Cursor
  770.           ret
  771. ENDP mov_lword
  772.  
  773.  
  774. ;----  mov_rword  ---------------------------------------------------------;
  775. ;  Purpose:    Moves cursor to start of next word.                         ;
  776. ;  Notes:      none                                                        ;
  777. ;  Entry:      BX = pointer to current position in commandline,            ;
  778. ;              CH = # of bytes to end of line.                             ;
  779. ;  Exit:       BX and CH adjusted as appropriate.                          ;
  780. ;  Calls:      find_StartofNextWord, advance_Cursor                        ;
  781. ;  Changes:    SI, (find_StartofNextWord)                                  ;
  782. ;              AX, BX, CH (advance_Cursor)                                 ;
  783. ;--------------------------------------------------------------------------;
  784. PROC mov_rword
  785.  
  786.           call      find_StartofNextWord
  787.           call      advance_Cursor
  788.           ret
  789. ENDP mov_rword
  790.  
  791.  
  792. ;----  mov_bol  -----------------------------------------------------------;
  793. ;  Purpose:    Moves cursor to start of commandline.                       ;
  794. ;  Notes:      none                                                        ;
  795. ;  Entry:      BX = pointer to current position in commandline,            ;
  796. ;              CH = # of bytes to end of line.                             ;
  797. ;  Exit:       BX = DX + 2,                                                ;
  798. ;              CH = # of characters in commandline,                        ;
  799. ;              SI = # of characters backed up.                             ;
  800. ;  Calls:      backup_Cursor                                               ;
  801. ;  Changes:    SI,                                                         ;
  802. ;              AX, BX, CH (backup_Cursor)                                  ;
  803. ;--------------------------------------------------------------------------;
  804. PROC mov_bol
  805.  
  806.           mov       si, bx
  807.           sub       si, dx
  808.           dec       si
  809.           dec       si                       ; SI = BX - (DX + 2)
  810.           call      backup_Cursor
  811.           ret
  812. ENDP mov_bol
  813.  
  814.  
  815. ;----  mov_eol  -----------------------------------------------------------;
  816. ;  Purpose:    Moves cursor to end of commandline.                         ;
  817. ;  Notes:      none                                                        ;
  818. ;  Entry:      BX = pointer to current position in commandline,            ;
  819. ;              CH = # of bytes to end of line.                             ;
  820. ;  Exit:       BX += CH,                                                   ;
  821. ;              CH = 0,                                                     ;
  822. ;              SI = # of characters advanced.                              ;
  823. ;  Calls:      advance_Cursor                                              ;
  824. ;  Changes:    SI,                                                         ;
  825. ;              AX, BX, CH (advance_Cursor)                                 ;
  826. ;--------------------------------------------------------------------------;
  827. PROC mov_eol
  828.  
  829.           mov       al, ch
  830.           ZERO      ah
  831.           mov       si, ax                   ; SI = CH
  832.           call      advance_Cursor
  833.           ret
  834. ENDP mov_eol
  835.  
  836.  
  837. ;----  mov_pcmd  ----------------------------------------------------------;
  838. ;  Purpose:    Replaces current with previous commandline from buffer.     ;
  839. ;  Notes:      none                                                        ;
  840. ;  Entry:      BX = pointer to current position in commandline,            ;
  841. ;              CH = # of bytes to end of line,                             ;
  842. ;              CL = # of bytes left in commandline.                        ;
  843. ;  Entry:      BX = pointer to end of new commandline,                     ;
  844. ;              CH = 0,                                                     ;
  845. ;              CL = # of bytes left in new commandline,                    ;
  846. ;              [CurCmd] adjusted.                                          ;
  847. ;  Calls:      recall_CmdFromBuf, del_line                                 ;
  848. ;  Changes:    CurCmd,                                                     ;
  849. ;              AX, BX, CH, CL, SI, (recall_CmdFromBuf)                     ;
  850. ;--------------------------------------------------------------------------;
  851. PROC mov_pcmd
  852.  
  853.           push      di
  854.  
  855. ; Point DI to 2 bytes before CurCmd. Abort if this lies at or before
  856. ; start of recall buffer.
  857.           mov       di, [cs:CurCmd]
  858.           dec       di                       ; now at possible CR
  859.           dec       di                       ; now at possible cmd's last char
  860.           cmp       di, OFFSET RecallBuf
  861.           jbe       SHORT @@Abort
  862.  
  863. ; Scan backwards to start of buffer or until finding another CR.
  864.           push      cx                       ; CH/CL for recall_CmdFromBuf
  865.           pushf
  866.           mov       al, CR
  867.           mov       cx, di
  868.           sub       cx, OFFSET RecallBuf - 1
  869.           std                                ; scan backwards
  870.           repne     scasb                    ; uses ES:DI
  871.           popf
  872.           pop       cx
  873.           inc       di                       ; should point to CR
  874.           cmp       [BYTE es:di], CR
  875.           jne       SHORT @@Abort
  876.  
  877. ; Point SI to start of command and recall it.
  878.           inc       di
  879.           mov       si, di
  880.           mov       [cs:CurCmd], si
  881.           call      recall_CmdFromBuf
  882.           jmp       SHORT @@Fin
  883.  
  884. ; Nothing to recall, so point CurCmd to start of buffer
  885. ; and delete current line.
  886. @@Abort:
  887.           mov       [cs:CurCmd], OFFSET RecallBuf
  888.           call      del_line
  889.  
  890. @@Fin:
  891.           pop       di
  892.           ret
  893. ENDP mov_pcmd
  894.  
  895.  
  896. ;----  mov_ncmd  ----------------------------------------------------------;
  897. ;  Purpose:    Replaces current with next commandline from buffer.         ;
  898. ;  Notes:      none                                                        ;
  899. ;  Entry:      BX = pointer to current position in commandline,            ;
  900. ;              CH = # of bytes to end of line,                             ;
  901. ;              CL = # of bytes left in commandline.                        ;
  902. ;  Entry:      BX = pointer to end of new commandline,                     ;
  903. ;              CH = 0,                                                     ;
  904. ;              CL = # of bytes left in new commandline,                    ;
  905. ;              [CurCmd] adjusted.                                          ;
  906. ;  Calls:      recall_CmdFromBuf, del_line                                 ;
  907. ;  Changes:    [CurCmd],                                                   ;
  908. ;              AX, BX, CH, CL, SI (recall_CmdFromBuf)                      ;
  909. ;--------------------------------------------------------------------------;
  910. PROC mov_ncmd
  911.  
  912.           push      di
  913.  
  914. ; Point DI to CurCmd. Abort if this lies at or after LastByte.
  915.           mov       di, [cs:CurCmd]
  916.           cmp       di, OFFSET LastByte
  917.           jae       SHORT @@Abort
  918.  
  919. ; Scan forwards to end of buffer or until finding another CR.
  920. ; NB: Scan stops before final CR in the recall buffer since
  921. ; there can never be a command after that; saves having to
  922. ; check DI against OFFSET LastByte.
  923.           push      cx                       ; CH/CL for recall_CmdFromBuf
  924.           mov       al, CR
  925.           mov       cx, OFFSET LastByte      ; *not* OFFSET LastByte + 1
  926.           sub       cx, di
  927.           repne     scasb                    ; uses ES:DI
  928.           pop       cx
  929.           dec       di                       ; should point to CR
  930.           cmp       [BYTE es:di], CR
  931.           jne       SHORT @@Abort
  932.  
  933. ; Point SI to start of command and recall it.
  934.           inc       di                       ; point to 1st char in next cmd
  935.           mov       si, di
  936.           mov       [cs:CurCmd], si
  937.           call      recall_CmdFromBuf
  938.           jmp       SHORT @@Fin
  939.  
  940. ; Nothing to recall, so point CurCmd to just past end
  941. ; of recall buffer and delete current line.
  942. @@Abort:
  943.           mov       [cs:CurCmd], OFFSET LastByte + 1
  944.           call      del_line
  945.  
  946. @@Fin:
  947.           pop       di
  948.           ret
  949. ENDP mov_ncmd
  950.  
  951.  
  952. ;----  del_lchar  ---------------------------------------------------------;
  953. ;  Purpose:    Deletes character to left of cursor.                        ;
  954. ;  Notes:      none                                                        ;
  955. ;  Entry:      BX = pointer to current position in commandline,            ;
  956. ;              CL = # of bytes left in commandline.                        ;
  957. ;  Exit:       BX--,                                                       ;
  958. ;              CL++.                                                       ;
  959. ;  Calls:      mov_lchar, delete_Chars                                     ;
  960. ;  Changes:    BX, (mov_lchar),                                            ;
  961. ;              AX, CH, CL, SI (delete_Chars)                               ;
  962. ;--------------------------------------------------------------------------;
  963. PROC del_lchar
  964.  
  965.           call      mov_lchar                ; sets SI = 0 (at bol) or 1
  966.           call      delete_Chars
  967.           ret
  968. ENDP del_lchar
  969.  
  970.  
  971. ;----  del_rchar  ---------------------------------------------------------;
  972. ;  Purpose:    Deletes character at cursor.                                ;
  973. ;  Notes:      none                                                        ;
  974. ;  Entry:      BX = pointer to current position in commandline,            ;
  975. ;              CH = # of bytes to end of line,                             ;
  976. ;              CL = # of bytes left in commandline.                        ;
  977. ;  Exit:       BX--,                                                       ;
  978. ;              CH--,                                                       ;
  979. ;              CL++.                                                       ;
  980. ;  Calls:      delete_Chars                                                ;
  981. ;  Changes:    AX, CH, CL, SI (delete_Chars)                               ;
  982. ;--------------------------------------------------------------------------;
  983. PROC del_rchar
  984.  
  985.           or        ch, ch
  986.           jz        SHORT @@Fin              ; abort if already at eol
  987.           mov       si, 1
  988.           call      delete_Chars
  989. @@Fin:
  990.           ret
  991. ENDP del_rchar
  992.  
  993.  
  994. ;----  del_lword  ---------------------------------------------------------;
  995. ;  Purpose:    Deletes word to left of cursor.                             ;
  996. ;  Notes:      none                                                        ;
  997. ;  Entry:      BX = pointer to current position in commandline,            ;
  998. ;              CH = # of bytes to end of line,                             ;
  999. ;              CL = # of bytes left in commandline.                        ;
  1000. ;  Exit:       BX, CH, and CL adjusted as appropriate.                     ;
  1001. ;  Calls:      mov_lword, delete_Chars                                     ;
  1002. ;  Changes:    BX, (mov_lword),                                            ;
  1003. ;              AX, CH, CL, SI (delete_Chars)                               ;
  1004. ;--------------------------------------------------------------------------;
  1005. PROC del_lword
  1006.  
  1007.           call      mov_lword                ; sets SI = 0 (at bol) or > 0
  1008.           call      delete_Chars
  1009.           ret
  1010. ENDP del_lword
  1011.  
  1012.  
  1013. ;----  del_rword  ---------------------------------------------------------;
  1014. ;  Purpose:    Deletes word to right of cursor.                            ;
  1015. ;  Notes:      none                                                        ;
  1016. ;  Entry:      BX = pointer to current position in commandline,            ;
  1017. ;              CH = # of bytes to end of line,                             ;
  1018. ;              CL = # of bytes left in commandline.                        ;
  1019. ;  Exit:       CH, and CL adjusted as appropriate.                         ;
  1020. ;  Calls:      find_StartofNextWord, delete_Chars                          ;
  1021. ;  Changes:    AX, CH, CL, SI (delete_Chars)                               ;
  1022. ;--------------------------------------------------------------------------;
  1023. PROC del_rword
  1024.  
  1025.           call      find_StartofNextWord     ; sets SI = 0 (at eol) or > 0
  1026.           call      delete_Chars
  1027.           ret
  1028. ENDP del_rword
  1029.  
  1030.  
  1031. ;----  del_bol  -----------------------------------------------------------;
  1032. ;  Purpose:    Deletes from cursor to start of commandline.                ;
  1033. ;  Notes:      none                                                        ;
  1034. ;  Entry:      BX = pointer to current position in commandline,            ;
  1035. ;              CH = # of bytes to end of line,                             ;
  1036. ;              CL = # of bytes left in commandline.                        ;
  1037. ;  Exit:       BX = DX + 2,                                                ;
  1038. ;              CH = number of characters in commandline,                   ;
  1039. ;              CL -= BX - DX - 2.                                          ;
  1040. ;  Calls:      mov_bol, delete_Chars                                       ;
  1041. ;  Changes:    BX, (mov_bol)                                               ;
  1042. ;              AX, CH, CL, SI (delete_Chars)                               ;
  1043. ;--------------------------------------------------------------------------;
  1044. PROC del_bol
  1045.  
  1046.           call      mov_bol                  ; sets SI = 0 (at bol) or > 0
  1047.           call      delete_Chars
  1048.           ret
  1049. ENDP del_bol
  1050.  
  1051.  
  1052. ;----  del_eol  -----------------------------------------------------------;
  1053. ;  Purpose:    Deletes from cursor to end of commandline.                  ;
  1054. ;  Notes:      none                                                        ;
  1055. ;  Entry:      BX = pointer to current position in commandline,            ;
  1056. ;              CH = # of bytes to end of line,                             ;
  1057. ;              CL = # of bytes left in commandline.                        ;
  1058. ;  Exit:       BX = DX + 2,                                                ;
  1059. ;              CH = 0,                                                     ;
  1060. ;              CL -= CH.                                                   ;
  1061. ;  Calls:      delete_Chars                                                ;
  1062. ;  Changes:    AX, CH, CL, SI (delete_Chars)                               ;
  1063. ;--------------------------------------------------------------------------;
  1064. PROC del_eol
  1065.  
  1066.           mov       al, ch
  1067.           ZERO      ah
  1068.           mov       si, ax
  1069.           call      delete_Chars
  1070.           ret
  1071. ENDP del_eol
  1072.  
  1073.  
  1074. ;----  del_line  ----------------------------------------------------------;
  1075. ;  Purpose:    Deletes entire commandline.                                 ;
  1076. ;  Notes:      none                                                        ;
  1077. ;  Entry:      BX = pointer to current position in commandline,            ;
  1078. ;              CH = # of bytes to end of line,                             ;
  1079. ;              CL = # of bytes left in commandline.                        ;
  1080. ;  Exit:       BX = DX + 2,                                                ;
  1081. ;              CH = 0,                                                     ;
  1082. ;              CL = [DX].                                                  ;
  1083. ;  Calls:      mov_bol, del_eol                                            ;
  1084. ;  Changes:    BX, (mov_bol)                                               ;
  1085. ;              AX, CH, CL, SI (del_eol)                                    ;
  1086. ;--------------------------------------------------------------------------;
  1087. PROC del_line
  1088.  
  1089.           call      mov_bol
  1090.           call      del_eol
  1091.           ret
  1092. ENDP del_line
  1093.  
  1094.  
  1095. ;----  del_buf  -----------------------------------------------------------;
  1096. ;  Purpose:    Deletes all commands in recall buffer.                      ;
  1097. ;  Notes:      Does not affect current commandline.                        ;
  1098. ;              This function is not documented elsewhere.                  ;
  1099. ;  Entry:      n/a                                                         ;
  1100. ;  Exit:       [CurCmd] = OFFSET LastByte + 1.                             ;
  1101. ;  Calls:      init_Buf                                                    ;
  1102. ;  Changes:    AX, [CurCmd] (init_Buf)                                     ;
  1103. ;--------------------------------------------------------------------------;
  1104. PROC del_buf
  1105.  
  1106.           mov       [WORD cs:CurCmd], 0
  1107.           call      init_Buf                 ; nb: changes CurCmd
  1108.           ret
  1109. ENDP del_buf
  1110.  
  1111.  
  1112. ;----  toggle_InsMode  ----------------------------------------------------;
  1113. ;  Purpose:    Toggles flag for insert mode.                               ;
  1114. ;  Notes:      none                                                        ;
  1115. ;  Entry:      n/a                                                         ;
  1116. ;  Exit:       [InsMode] toggled.                                          ;
  1117. ;  Calls:      none                                                        ;
  1118. ;  Changes:    [InsMode]                                                   ;
  1119. ;--------------------------------------------------------------------------;
  1120. PROC toggle_InsMode
  1121.  
  1122.           xor       [cs:InsMode], 1
  1123.           ret
  1124. ENDP toggle_InsMode
  1125.  
  1126.  
  1127. ;----  init_Buf  ----------------------------------------------------------;
  1128. ;  Purpose:    Initializes recall buffer if necessary.                     ;
  1129. ;  Notes:      Clears recall buffer if CurCmd is zero. Normally, CurCmd    ;
  1130. ;                   will take on values OFFSET RecallBuf and LastByte,     ;
  1131. ;                   or less. Zero should not otherwise occur.              ;
  1132. ;              This is needed when scanning for previous commands -        ;
  1133. ;                   spurious CRs should not be encountered.                ;
  1134. ;  Entry:      [CurCmd] = pointer to current command in recall buffer.     ;
  1135. ;  Exit:       [CurCmd] = OFFSET LastByte + 1 if buffer is initialized.    ;
  1136. ;  Calls:      none                                                        ;
  1137. ;  Changes:    AX, [CurCmd] possibly                                       ;
  1138. ;--------------------------------------------------------------------------;
  1139. PROC init_Buf
  1140.  
  1141. ; Abort if [CurCmd] is non-zero.
  1142.           cmp       [WORD cs:CurCmd], 0
  1143.           jne       SHORT @@Fin
  1144.  
  1145. ; Initialize buffer by zeroing out all but last byte. There put a CR
  1146. ; so when searching backwards for commands I'll find at least one.
  1147.           push      cx di
  1148.           ZERO      al                       ; fill with zeros
  1149.           mov       cx, BUFSIZE - 1
  1150.           mov       di, OFFSET RecallBuf
  1151.           rep       stosb                    ; uses ES:DI
  1152.           mov       [BYTE es:di], CR         ; buffer ends with CR
  1153.           pop       di cx
  1154.  
  1155. ; Point current command to past end of recall buffer. This is so both
  1156. ; mov_pcmd and mov_ncmd will not find any commands yet still function
  1157. ; without error (which would happen if [CurCmd] were left at 0).
  1158.           mov       [cs:CurCmd], OFFSET LastByte + 1
  1159.  
  1160. @@Fin:
  1161.           ret
  1162. ENDP init_Buf
  1163.  
  1164.  
  1165. ;----  get_CmdLine  -------------------------------------------------------;
  1166. ;  Purpose:    Reads a commandline from user.                              ;
  1167. ;  Notes:      The caller's buffer is used as a scratch area to keep       ;
  1168. ;                   memory requirements to a minimum.                      ;
  1169. ;  Entry:      DS:DX = buffer for storing commandline,                     ;
  1170. ;              [BYTE DS:DX] = maximum number of bytes to read.             ;
  1171. ;  Exit:       [BYTE DS:DX+1] = number of bytes actually read,             ;
  1172. ;              [BYTE DS:DX+2] = 1st byte read from user.                   ;
  1173. ;  Calls:      get_KeyNoEcho, add_CharToLine, [CmdTbl]                     ;
  1174. ;  Changes:    AX, BX, CX, BP, [InsMode], [PrevCmd], [NextCmd]             ;
  1175. ;--------------------------------------------------------------------------;
  1176. PROC get_CmdLine
  1177.  
  1178.           mov       bx, dx                   ; BX used for indexed addressing
  1179.           ZERO      ch                       ; bytes to end of line
  1180.           mov       cl, [bx]                 ; space left in buffer
  1181.           dec       cl                       ;    less 1 for final CR
  1182.           inc       bx                       ; pointer to first spot in buffer
  1183.           inc       bx
  1184.  
  1185. ; Get key and determine if it's an editing key or a regular character.
  1186. @@NewKey:
  1187.           call      get_KeyNoEcho            ; get key from user
  1188.           cmp       al, CR                   ; is user done yet?
  1189.           jz        SHORT @@Fin
  1190.           cmp       al, LF                   ; skip LF if stdin redirected
  1191.           jz        SHORT @@NewKey
  1192.           cmp       al, BS                   ; BS is an editing key
  1193.           je        SHORT @@EditKey
  1194.           cmp       al, ESCAPE               ; ESCAPE is another
  1195.           je        SHORT @@EditKey
  1196.           or        al, al                   ; was key zero?
  1197.           jnz       SHORT @@RegularChar      ;   no, then it's a regular char
  1198.           call      get_KeyNoEcho            ;   yes, get extended scan code
  1199.  
  1200. ; Process extended key as an editing key. Invalid keys are not added
  1201. ; to the commandline buffer; instead, they merely result in a bell.
  1202. @@EditKey:
  1203.           mov       bp, OFFSET CmdTbl        ; point to table of editing cmds
  1204.  
  1205. @@NewCmd:
  1206.           cmp       [(CMD PTR cs:bp).Key], al
  1207.           je        SHORT @@ProcessCmd
  1208.           add       bp, SIZE CmdTbl
  1209.           cmp       [(CMD PTR cs:bp).Key], 0 ; zero marks end of table
  1210.           jne       SHORT @@NewCmd           ;   and must point to ring_Bell
  1211.                                              ;   so execution drops thru!!!
  1212. @@ProcessCmd:
  1213.           call      [(CMD PTR cs:bp).Function]
  1214.           jmp       SHORT @@NewKey
  1215.  
  1216. ; It's an ordinary character so add it to the commandline.
  1217. @@RegularChar:
  1218.           call      add_CharToLine
  1219.           jmp       SHORT @@NewKey
  1220.  
  1221. ; Now determine number of bytes in buffer, put that count in [DX+1], 
  1222. ; and terminate buffer with a CR. NB: count excludes final CR.
  1223. @@Fin:
  1224.           mov       bx, dx                   ; point back to start of buffer
  1225.           mov       al, [bx]                 ; compute count
  1226.           sub       al, cl
  1227.           dec       al                       ; exclude final CR
  1228.           ZERO      ah
  1229.           mov       [bx+1], al               ; [DS:DX+1] = count
  1230.           add       bx, ax
  1231.           mov       [BYTE bx+2], CR          ; place CR at end of buffer
  1232.           ret
  1233. ENDP get_CmdLine
  1234.  
  1235.  
  1236. ;----  store_CmdInBuf  ----------------------------------------------------;
  1237. ;  Purpose:    Stores the commandline at the end of the recall buffer.     ;
  1238. ;  Notes:      Commandlines consisting of 1 character (CR) are not saved.  ;
  1239. ;  Entry:      DS:DX = pointer to start of commandline,                    ;
  1240. ;              [BYTE DS:DX+1] = maximum number of bytes to read.           ;
  1241. ;  Exit:       [CurCmd] = LastByte + 1.                                    ;
  1242. ;  Calls:      none                                                        ;
  1243. ;  Changes:    AX, BX, CX, DI, SI, [CurCmd]                                ;
  1244. ;--------------------------------------------------------------------------;
  1245. PROC store_CmdInBuf
  1246.  
  1247. ; Check length of commandline.
  1248.           mov       bx, dx
  1249.           mov       cl, [bx+1]
  1250.           or        cl, cl                   ; CL does not include final CR
  1251.           jz        SHORT @@Fin              ; so if = 0, nothing's there
  1252.           inc       cl                       ; else set CX = # bytes in cmd
  1253.           ZERO      ch                       ;   *including* final CR
  1254.  
  1255. ; Make room in recall buffer for commandline by shifting everything
  1256. ; back by [DS:DX+1] characters. 
  1257.           push      cx ds                    ; need both to copy to recall buf
  1258.           mov       ax, cs
  1259.           mov       ds, ax
  1260.           mov       di, OFFSET RecallBuf     ; to start of buffer
  1261.           mov       si, di
  1262.           add       si, cx                   ; from start + CX
  1263.           neg       cx
  1264.           add       cx, BUFSIZE              ; for BUFSIZE - [BYTE BX+1]
  1265.           rep       movsb                    ; move them
  1266.           pop       ds cx
  1267.  
  1268. ; Add commandline in empty space at end of recall buffer. By this
  1269. ; point DI will point to space for current commandline.
  1270.           mov       si, bx
  1271.           inc       si
  1272.           inc       si
  1273.           rep       movsb                    ; from DS:SI to ES:DI
  1274.           mov       [cs:CurCmd], OFFSET LastByte + 1
  1275.  
  1276. @@Fin:
  1277.           ret
  1278. ENDP store_CmdInBuf
  1279.  
  1280.  
  1281. ;----  do_Int21  ----------------------------------------------------------;
  1282. ;  Purpose:    Passes calls to input strings along to my own handler.      ;
  1283. ;  Notes:      none                                                        ;
  1284. ;  Entry:      AH = subfunction to perform                                 ;
  1285. ;  Exit:       If AH = 10, DS:DX points to buffer read from user.          ;
  1286. ;  Calls:      init_Buf, get_CmdLine, store_CmdInBuf                       ;
  1287. ;  Changes:    flags                                                       ;
  1288. ;--------------------------------------------------------------------------;
  1289. PROC do_Int21   FAR
  1290.  
  1291. ; This structure is used to share intrrupts. The real entry point
  1292. ; follows immediately after it.
  1293. my_Int21  ISR       < , , , 0, ((@@hw_reset - $ - 2) SHL 8 + 0ebh), >
  1294.  
  1295. ; If the call is for buffered input, then use my handler;
  1296. ; otherwise, pass it along to the old handler.
  1297.           cmp       ah, 10
  1298.           jz        SHORT @@SwitchStack
  1299.           jmp       [cs:my_Int21.OldISR]     ;   no, pass it along
  1300.                                              ;      nb: old vector issues IRET
  1301.  
  1302. ; Switch over to my own stack and save callers registers.
  1303. @@SwitchStack:
  1304.           mov       [cs:OldAX], ax           ; can't push it on my stack yet
  1305.           cli                                ; critical part - disallow INTs
  1306.           mov       [WORD cs:OldStack], sp
  1307.           mov       [WORD cs:OldStack+2], ss
  1308.           mov       ax, cs
  1309.           mov       ss, ax
  1310.           mov       sp, OFFSET StackTop
  1311.           sti                                ; ok, out of critical section
  1312.           push      bx cx dx di si bp ds es
  1313.  
  1314. ; Meat of my interrupt handler.
  1315.           mov       es, ax                   ; set ES = CX
  1316.           cld
  1317.           call      init_Buf
  1318.           call      get_CmdLine
  1319.           call      store_CmdInBuf
  1320.  
  1321. ; Restore caller's registers.
  1322.           pop       es ds bp si di dx cx bx
  1323.           cli
  1324.           mov       ss, [WORD cs:OldStack+2]
  1325.           mov       sp, [WORD cs:OldStack]
  1326.           sti
  1327.           mov       ax, [cs:OldAX]
  1328.  
  1329.           iret                               ; return to caller
  1330.  
  1331. ; Required for IBM Interrupt Sharing Protocol. Normally it is used
  1332. ; only by hardware interrupt handlers.
  1333. @@hw_reset:
  1334.           retf
  1335. ENDP do_Int21
  1336.  
  1337.  
  1338. ;----  do_Int2D  ----------------------------------------------------------;
  1339. ;  Purpose:    Handle INT 2D.                                              ;
  1340. ;  Notes:      Only the install check is truly supported.                  ;
  1341. ;  Entry:      AH = Multiplex ID,                                          ;
  1342. ;              AL = function code                                          ;
  1343. ;  Exit:       AL = FF in the case of an install check,                    ;
  1344. ;              CX = TSR version,                                           ;
  1345. ;              DX:DI points to resident copy of TSR signature.             ;
  1346. ;  Calls:      n/a                                                         ;
  1347. ;  Changes:    AL, CX, DX, DI                                              ;
  1348. ;--------------------------------------------------------------------------;
  1349. PROC do_Int2D  FAR
  1350.  
  1351. ; This structure is used to share intrrupts. The real entry point
  1352. ; follows immediately after it.
  1353. my_Int2D  ISR       < , , , 0, ((@@hw_reset - $ - 2) SHL 8 + 0ebh), >
  1354.  
  1355. ; Test if request is for me. Pass it along to next ISR in chain if not.
  1356.           cmp       ah, [cs:MPlex]           ; my multiplex ID?
  1357.           jz        SHORT @@forMe            ;   yes
  1358.           jmp       [cs:my_Int2d.OldISR]     ;   no, pass it along
  1359.                                              ;      nb: old vector issues IRET
  1360.  
  1361. ; Check function as specified in AL.
  1362. @@forMe:
  1363.           cmp       al, 0                    ; installation check
  1364.           jz        SHORT @@InstallCheck
  1365.           cmp       al, 1                    ; get entry point
  1366.           jz        SHORT @@GetEntryPoint
  1367.           cmp       al, 2                    ; uninstall
  1368.           jz        SHORT @@Uninstall
  1369.           ZERO      al                       ; mark as not implemented
  1370.           jmp       SHORT @@Fin
  1371.  
  1372. @@InstallCheck:
  1373.           dec       al                       ; set AL = FF
  1374.           mov       cx, [cs:TSR_Ver]         ; CH = major; CL = minor
  1375.           mov       dx, cs                   ; DX:DI points to sig string
  1376.           mov       di, OFFSET TSR_Sig
  1377.           jmp       SHORT @@Fin
  1378.  
  1379. @@GetEntryPoint:
  1380.           ZERO      al                       ; mark as not supported
  1381.           jmp       SHORT @@Fin
  1382.  
  1383. @@Uninstall:
  1384.           ZERO      al                       ; not implemented in API
  1385.           jmp       SHORT @@Fin
  1386.  
  1387. @@Fin:
  1388.           iret                               ; return to caller
  1389.  
  1390. ; Required for IBM Interrupt Sharing Protocol. Normally it is used
  1391. ; only by hardware interrupt handlers.
  1392. @@hw_reset:
  1393.           retf
  1394. ENDP do_Int2D
  1395.  
  1396.  
  1397. %NEWPAGE
  1398. ;--------------------------------------------------------------------------;
  1399. ;                        R E C A L L   B U F F E R                         ;
  1400. ;--------------------------------------------------------------------------;
  1401. RecallBuf =         $                        ; will overlay transient portion
  1402. LastByte  =         RecallBuf + BUFSIZE - 1  ; room for BUFSIZE characters
  1403.                                              ; and end of resident portion
  1404.  
  1405.  
  1406. %NEWPAGE
  1407. ;--------------------------------------------------------------------------;
  1408. ;                       T R A N S I E N T   D A T A                        ;
  1409. ;--------------------------------------------------------------------------;
  1410. ProgName  DB        'recall: '
  1411.           DB        EOS
  1412. EOL       DB        '.', CR, LF
  1413.           DB        EOS
  1414. HelpMsg   DB        CR, LF
  1415.           DB        'TifaWARE RECALL, v', VERSION, ', ', ??Date
  1416.           DB        ' - commandline editor and history TSR.', CR, LF
  1417.           DB        'Usage: recall [-options]', CR, LF, LF
  1418.           DB        'Options:', CR, LF
  1419.           DB        '  -i = install in memory', CR, LF
  1420.           DB        '  -l = list commandlines in recall buffer', CR, LF
  1421.           DB        '  -r = remove from memory', CR, LF
  1422.           DB        '  -? = display this help message', CR, LF, LF
  1423.           DB        'Only one option can be specified at a time.'
  1424.           DB        CR, LF, EOS
  1425. ErrMsgOpt DB        'illegal option -- '
  1426. OptCh     DB        ?                        ; room for offending character
  1427.           DB        EOS
  1428. ErrMsgVer DB        'DOS v1 is not supported'
  1429.           DB        EOS
  1430. ErrMsgRes DB        'unable to go resident'
  1431.           DB        EOS
  1432. ErrMsgRem DB        'unable to remove from memory'
  1433.           DB        EOS
  1434. ErrMsgNYI DB        'not yet installed'
  1435.           DB        EOS
  1436. InstalMsg DB        'TifaWARE RECALL, v', VERSION
  1437.           DB        ' now installed.'
  1438.           DB        CR, LF, EOS
  1439. RemoveMsg DB        'successfully removed'
  1440.           DB        EOS
  1441.  
  1442. SwitCh    DB        '-'                      ; char introducing options
  1443. HFlag     DB        0                        ; flag for on-line help
  1444. IFlag     DB        0                        ; flag for installing TSR
  1445. LFlag     DB        0                        ; flag for listing commandlines
  1446. RFlag     DB        0                        ; flag for removing TSR
  1447.  
  1448.  
  1449. %NEWPAGE
  1450. ;--------------------------------------------------------------------------;
  1451. ;                       T R A N S I E N T   C O D E                        ;
  1452. ;--------------------------------------------------------------------------;
  1453. ;----  go_Resident  -------------------------------------------------------;
  1454. ;  Purpose:    Attempts to make TSR resident.                              ;
  1455. ;  Notes:      Aborts if there's not enough memory to satisfy request.     ;
  1456. ;              This procedure ONLY EXITS ON ERROR.                         ;
  1457. ;  Entry:      DS = segment address of program's PSP, which also holds     ;
  1458. ;                   HookTbl, a structure of type ISRHOOK.                  ;
  1459. ;  Exit:       none                                                        ;
  1460. ;  Calls:      check_ifInstalled, fputs, fake_Env, install_TSR, errmsg     ;
  1461. ;  Changes:    AX, BX, CX, DX, DI, SI, ES                                  ;
  1462. ;--------------------------------------------------------------------------;
  1463. PROC go_Resident
  1464.  
  1465. ; See if there's already a copy resident. nb: only interested in AX
  1466. ; on return from the install check.
  1467.           mov       si, OFFSET TSR_SIG
  1468.           call      check_ifInstalled        ; -> AX, CX, and DX:DI
  1469.           cmp       al, 2                    ; out of multiplex ids?
  1470.           jz        SHORT @@Abort            ;   yes, abort
  1471.           cmp       al, 1                    ; already loaded?
  1472.           jz        SHORT @@Abort            ;   yes
  1473.           mov       [MPlex], ah              ; save mplex id
  1474.  
  1475. ; This is the point of no-return -- if we get here we're going resident.
  1476.           mov       bx, STDOUT
  1477.           mov       dx, OFFSET InstalMsg
  1478.           call      fputs
  1479.  
  1480. ; Create a fake environment and free existing one.
  1481. ; Make sure that ES points to PSP.
  1482.           ZERO      cx                       ; tells fake_Env to fake it
  1483.           push      ds
  1484.           pop       es
  1485.           call      fake_Env
  1486.  
  1487. ; Ok, all that's left is to go resident.
  1488. ; ****************************************************************************
  1489. ; NB: TASM's IDEAL mode treats arguments of the OFFSET operator in a peculiar 
  1490. ; fashion, as can be seen by browsing the lexical grammer in Appendix A of
  1491. ; the _Reference Guide_. If MASM mode were used, the expression below would
  1492. ; be written "(OFFSET LastByte - OFFSET SegStart + 16) SHR 5". However, in
  1493. ; IDEAL mode not only would "OFFSET SegStart + 16" be parsed as "OFFSET
  1494. ; (SegStart + 16)" but also the result would be viewed as a relative quantity.
  1495. ; As it is, TASM replaces labels below with their respective address values 
  1496. ; thereby computing the "correct" amount of memory to save.
  1497. ; ****************************************************************************
  1498. ; NB: While Angermayer and Jaeger in their book say 15 should be used
  1499. ; below, I've found 16 is necessary to handle cases in which LastByte
  1500. ; lies at the start of a paragraph. So what if I'm wasting an entire
  1501. ; paragraph!
  1502. ; ****************************************************************************
  1503.           mov       dx, (LastByte - SegStart + 16) SHR 4
  1504.           mov       bx, OFFSET HookTbl       ; pointer to ISRHOOK structure
  1505.           call      install_TSR              ; never returns
  1506.  
  1507. ; Execution gets here only on error because:
  1508. ;  - all multiplex ids are in use! 
  1509. ;  - the TSR is already resident.
  1510. @@Abort:
  1511.           mov       dx, OFFSET ErrMsgRes     ; "unable to go resident"
  1512.           call      errmsg
  1513.           ret
  1514. ENDP go_Resident
  1515.  
  1516.  
  1517. ;----  clear_Resident  ----------------------------------------------------;
  1518. ;  Purpose:    Attempts to remove a TSR from memory.                       ;
  1519. ;  Notes:      none                                                        ;
  1520. ;  Entry:      DS = segment address of program's PSP.                      ;
  1521. ;  Exit:       AL = 0 if removal succeeded; ERRNYI if not installed;       ;
  1522. ;                   ERRUNI otherwise.                                      ;
  1523. ;  Calls:      check_ifInstalled, remove_TSR, errmsg                       ;
  1524. ;  Changes:    AX, BX, CX, DX, DI, SI, ES                                  ;
  1525. ;--------------------------------------------------------------------------;
  1526. PROC clear_Resident
  1527.  
  1528. ; See if there's already a copy resident.
  1529.           mov       si, OFFSET TSR_SIG
  1530.           call      check_ifInstalled        ; DS:SI -> AX, CX, DX:DI
  1531.           cmp       al, 1                    ; already loaded?
  1532.           jz        SHORT @@Removal          ;   yes
  1533.           mov       al, ERRNYI               ;   no, set return code
  1534.           mov       dx, OFFSET ErrMsgNYI     ;     "not yet installed"
  1535.           jmp       SHORT @@Fin
  1536.  
  1537. ; Try to remove it.
  1538. @@Removal:
  1539.           mov       bx, OFFSET HookTbl       ; HookTbl in resident data area
  1540.           mov       es, dx                   ; install check returns DX:DI
  1541.           call      remove_TSR               ; ES:BX -> n/a
  1542.           jc        SHORT @@Abort
  1543.           ZERO      al
  1544.           mov       dx, OFFSET RemoveMsg
  1545.           jmp       SHORT @@Fin
  1546.  
  1547. @@Abort:
  1548.           mov       al, ERRUNI
  1549.           mov       dx, OFFSET ErrMsgRem     ; "unable to remove"
  1550.  
  1551. @@Fin:
  1552.           call      errmsg
  1553.           ret
  1554. ENDP clear_Resident
  1555.  
  1556.  
  1557. ;----  list_CmdLines  -----------------------------------------------------;
  1558. ;  Purpose:    Lists commandlines in recall buffer.                        ;
  1559. ;  Notes:      none                                                        ;
  1560. ;  Entry:      none                                                        ;
  1561. ;  Exit:       AL = 0 if successful; ERRNYI otherwise.                     ;
  1562. ;  Calls:      check_ifInstalled, errmsg                                   ;
  1563. ;  Changes:    AX, CX, DX, DI, SI, ES                                      ;
  1564. ;--------------------------------------------------------------------------;
  1565. PROC list_CmdLines
  1566.  
  1567.           mov       si, OFFSET TSR_SIG
  1568.           call      check_ifInstalled        ; DS:SI -> AX, CX, DX:DI
  1569.           cmp       al, 1                    ; already loaded?
  1570.           jnz       SHORT @@Abort            ;   no
  1571.           push      ds
  1572.           mov       ds, dx                   ; point DS into resident data
  1573.           mov       es, dx                   ; ES too
  1574.  
  1575. ; Point to start of 1st complete command in recall buffer. NB:
  1576. ; there will always be at least one command - "recall -l".
  1577.           mov       al, CR
  1578.           mov       cx, BUFSIZE
  1579.           mov       di, OFFSET RecallBuf
  1580.           repne     scasb                    ; uses ES:DI
  1581.  
  1582. ; Display rest of buffer. NB: This is done character one character at
  1583. ; a time because buffered lines end with CR, not 0.
  1584.           mov       ah, 2                    ; DOS subfunction to display char
  1585. @@NextChar:
  1586.           mov       dl, [di]                 ; get char
  1587.           inc       di
  1588.           int       DOS                      ; display it
  1589.           cmp       dl, CR                   ; need to display CR/LF?
  1590.           loopne    SHORT @@NextChar         ; always decrements CX 
  1591.           mov       dl, LF                   ; display LF now
  1592.           int       DOS
  1593.           or        cx, cx                   ; done yet?
  1594.           jnz       SHORT @@NextChar
  1595.  
  1596.           pop       ds
  1597.           ZERO      al                       ; flag no error
  1598.           jmp       SHORT @@Fin
  1599.  
  1600. @@Abort:
  1601.           mov       dx, OFFSET ErrMsgNYI
  1602.           call      errmsg
  1603.           mov       al, ERRNYI
  1604.  
  1605. @@Fin:
  1606.           ret
  1607. ENDP list_CmdLines
  1608.  
  1609.  
  1610. ;----  skip_Spaces  -------------------------------------------------------;
  1611. ;  Purpose:    Skips past spaces in a string.                              ;
  1612. ;  Notes:      Scanning stops with either a non-space *OR* CX = 0.         ;
  1613. ;  Entry:      DS:SI = start of string to scan.                            ;
  1614. ;  Exit:       AL = next non-space character,                              ;
  1615. ;              CX is adjusted as necessary,                                ;
  1616. ;              DS:SI = pointer to next non-space.                          ;
  1617. ;  Calls:      none                                                        ;
  1618. ;  Changes:    AL, CX, SI                                                  ;
  1619. ;--------------------------------------------------------------------------;
  1620. PROC skip_Spaces
  1621.  
  1622.           jcxz      SHORT @@Fin
  1623. @@NextCh:
  1624.           lodsb
  1625.           cmp       al, ' '
  1626.           loopz     @@NextCh
  1627.           jz        SHORT @@Fin              ; CX = 0; don't adjust
  1628.  
  1629.           inc       cx                       ; adjust counters if cx > 0
  1630.           dec       si
  1631.  
  1632. @@Fin:
  1633.           ret
  1634. ENDP skip_Spaces
  1635.  
  1636.  
  1637. ;----  get_Opt  -----------------------------------------------------------;
  1638. ;  Purpose:    Get a commandline option.                                   ;
  1639. ;  Notes:      none                                                        ;
  1640. ;  Entry:      AL = option character,                                      ;
  1641. ;  Exit:       n/a                                                         ;
  1642. ;  Calls:      tolower, errmsg                                             ;
  1643. ;  Changes:    AX, DX, [OptCh], [HFlag], [IFlag], [LFlag], [RFlag]         ;
  1644. ;--------------------------------------------------------------------------;
  1645. PROC get_Opt
  1646.  
  1647.           mov       [OptCh], al              ; save for later
  1648.           call      tolower                  ; use only lowercase in cmp.
  1649.           cmp       al, 'i'
  1650.           jz        SHORT @@OptI
  1651.           cmp       al, 'l'
  1652.           jz        SHORT @@OptL
  1653.           cmp       al, 'r'
  1654.           jz        SHORT @@OptR
  1655.           cmp       al, '?'
  1656.           jz        SHORT @@OptH
  1657.           mov       dx, OFFSET ErrMsgOpt     ; unrecognized option
  1658.           call      errmsg                   ; then *** DROP THRU *** to OptH
  1659.  
  1660. ; Various possible options.
  1661. @@OptH:
  1662.           mov       [HFlag], ON              ; set help flag
  1663.           jmp       SHORT @@Fin
  1664.  
  1665. @@OptI:
  1666.           mov       [IFlag], ON              ; install in memory
  1667.           jmp       SHORT @@Fin
  1668.  
  1669. @@OptL:
  1670.           mov       [LFlag], ON              ; list cmds in recall buffer
  1671.           jmp       SHORT @@Fin
  1672.  
  1673. @@OptR:
  1674.           mov       [RFlag], ON              ; remove from memory
  1675.  
  1676. @@Fin:
  1677.           ret
  1678. ENDP get_Opt
  1679.  
  1680.  
  1681. ;----  process_CmdLine  ---------------------------------------------------;
  1682. ;  Purpose:    Processes commandline arguments.                            ;
  1683. ;  Notes:      A switch character by itself is ignored.                    ;
  1684. ;  Entry:      n/a                                                         ;
  1685. ;  Exit:       n/a                                                         ;
  1686. ;  Calls:      skip_Spaces, get_Opt                                        ;
  1687. ;  Changes:    AX, CX, SI,                                                 ;
  1688. ;              DX, [OptCh], [HFlag], [IFlag], [LFlag], [RFlag] (get_Opt)   ;
  1689. ;              Direction flag is cleared.                                  ;
  1690. ;--------------------------------------------------------------------------;
  1691. PROC process_CmdLine
  1692.  
  1693.           cld                                ; forward, march!
  1694.           ZERO      ch
  1695.           mov       cl, [CmdLen]             ; length of commandline
  1696.           mov       si, OFFSET CmdLine       ; offset to start of commandline
  1697.  
  1698.           call      skip_Spaces              ; check if any args supplied
  1699.           or        cl, cl
  1700.           jnz       SHORT @@ArgLoop
  1701.  
  1702.           mov       [HFlag], ON              ; assume user needs help
  1703.           jmp       SHORT @@Fin
  1704.  
  1705. ; For each blank-delineated argument on the commandline...
  1706. @@ArgLoop:
  1707.           lodsb                              ; next character
  1708.           dec       cl
  1709.           cmp       al, [SwitCh]             ; is it the switch character?
  1710.           jnz       SHORT @@NonOpt           ;   no
  1711.  
  1712. ; Isolate each option and process it. Stop when a space is reached.
  1713. @@OptLoop:
  1714.           jcxz      SHORT @@Fin              ; abort if nothing left
  1715.           lodsb
  1716.           dec       cl
  1717.           cmp       al, ' '
  1718.           jz        SHORT @@NextArg          ; abort when space reached
  1719.           call      get_Opt
  1720.           jmp       @@OptLoop
  1721.  
  1722. ; Any argument which is *not* an option is invalid. Set help flag and abort.
  1723. @@NonOpt:
  1724.           mov       [HFlag], ON
  1725.           jmp       SHORT @@Fin
  1726.  
  1727. ; Skip over spaces until next argument is reached.
  1728. @@NextArg:
  1729.           call      skip_Spaces
  1730.           or        cl, cl
  1731.           jnz       @@ArgLoop
  1732.  
  1733. @@Fin:
  1734.           ret
  1735. ENDP process_CmdLine
  1736.  
  1737.  
  1738. ;----  main  --------------------------------------------------------------;
  1739. ;  Purpose:    Main section of program.                                    ;
  1740. ;  Notes:      none                                                        ;
  1741. ;  Entry:      Arguments as desired                                        ;
  1742. ;  Exit:       Return code as follows:                                     ;
  1743. ;                   0 => program ran successfully,                         ;
  1744. ;                   ERRH => on-line help supplied,                         ;
  1745. ;                   ERRINS => program could not be installed,              ;
  1746. ;                   ERRNYI => program was not yet installed.               ;
  1747. ;  Calls:      process_CmdLine, fputs, list_CmdLines, install_TSR,         ;
  1748. ;                   uninstall_TSR                                          ;
  1749. ;  Changes:    n/a                                                         ;
  1750. ;--------------------------------------------------------------------------;
  1751. main:
  1752.  
  1753. ; Must be running at least DOS v2.x.
  1754.           call      getvdos
  1755.           cmp       al, 2
  1756.           jae       SHORT @@ReadCmds
  1757.           mov       dx, OFFSET ErrMsgVer     ; gotta have at least DOS v2
  1758.           call      errmsg
  1759.  
  1760. ; Parse commandline.
  1761. @@ReadCmds:
  1762.           call      process_CmdLine          ; process commandline args
  1763.           cmp       [IFlag], ON              ; install it?
  1764.           je        SHORT @@Install
  1765.           cmp       [LFlag], ON              ; list commands?
  1766.           je        SHORT @@List
  1767.           cmp       [RFlag], ON              ; remove it?
  1768.           je        SHORT @@Remove
  1769.           mov       bx, STDERR               ; user must need help
  1770.           mov       dx, OFFSET HelpMsg
  1771.           call      fputs
  1772.           mov       al, ERRH
  1773.           jmp       SHORT @@Fin
  1774.  
  1775. @@List:
  1776.           call      list_CmdLines
  1777.           jmp       SHORT @@Fin
  1778.  
  1779. @@Install:
  1780.           call      go_Resident              ; returns on error only
  1781.           mov       al, ERRINS
  1782.           jmp       SHORT @@Fin
  1783.  
  1784. @@Remove:
  1785.           call      clear_Resident
  1786.  
  1787. ; Terminate the program using as return code what's in AL.
  1788. @@Fin:
  1789.           mov       ah, 4ch
  1790.           int       DOS
  1791. EVEN
  1792. ;-------------------------------------------------------------------------;
  1793. ;  Purpose:    Writes an ASCIIZ string to specified device.
  1794. ;  Notes:      A zero-length string doesn't seem to cause problems when
  1795. ;                 this output function is used.
  1796. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  1797. ;  Entry:      BX = device handle,
  1798. ;              DS:DX = pointer to string.
  1799. ;  Exit:       Carry flag set if EOS wasn't found or handle is invalid.
  1800. ;  Calls:      strlen
  1801. ;  Changes:    none
  1802. ;-------------------------------------------------------------------------;
  1803. PROC fputs
  1804.  
  1805.    push     ax cx di es
  1806.    mov      ax, ds
  1807.    mov      es, ax
  1808.    mov      di, dx
  1809.    call     strlen                        ; set CX = length of string
  1810.    jc       SHORT @@Fin                   ; abort if problem finding end
  1811.    mov      ah, 40h                       ; MS-DOS raw output function
  1812.    int      DOS
  1813. @@Fin:
  1814.    pop      es di cx ax
  1815.    ret
  1816.  
  1817. ENDP fputs
  1818.  
  1819.  
  1820. EVEN
  1821. ;-------------------------------------------------------------------------;
  1822. ;  Purpose:    Writes an error message to stderr.
  1823. ;  Notes:      none
  1824. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  1825. ;  Entry:      DS:DX = pointer to error message.
  1826. ;  Exit:       n/a
  1827. ;  Calls:      fputs
  1828. ;  Changes:    none
  1829. ;-------------------------------------------------------------------------;
  1830. PROC errmsg
  1831.  
  1832.    push     bx dx
  1833.    mov      bx, STDERR
  1834.    mov      dx, OFFSET ProgName           ; display program name
  1835.    call     fputs
  1836.    pop      dx                            ; recover calling parameters
  1837.    push     dx                            ; and save again to avoid change
  1838.    call     fputs                         ; display error message
  1839.    mov      dx, OFFSET EOL
  1840.    call     fputs
  1841.    pop      dx bx
  1842.    ret
  1843.  
  1844. ENDP errmsg
  1845.  
  1846.  
  1847. EVEN
  1848. ;-------------------------------------------------------------------------;
  1849. ;  Purpose:    Gets version of DOS currently running.
  1850. ;  Notes:      none
  1851. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  1852. ;  Entry:      n/a
  1853. ;  Exit:       AL = major version number,
  1854. ;              AH = minor version number (2.1 = 10).
  1855. ;  Calls:      none
  1856. ;  Changes:    AX
  1857. ;-------------------------------------------------------------------------;
  1858. PROC getvdos
  1859.  
  1860.    push     bx cx                         ; DOS destroys bx and cx!
  1861.    mov      ah, 30h
  1862.    int      DOS
  1863.    pop      cx bx
  1864.    ret
  1865.  
  1866. ENDP getvdos
  1867.  
  1868.  
  1869. EVEN
  1870. ;--------------------------------------------------------------------------;
  1871. ;  Purpose:    Gets address of an interrupt handler.
  1872. ;  Notes:      none
  1873. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  1874. ;  Entry:      AL = interrupt of interest.
  1875. ;  Exit:       ES:BX = address of current interrupt handler
  1876. ;  Calls:      none
  1877. ;  Changes:    ES:BX
  1878. ;--------------------------------------------------------------------------;
  1879. PROC getvect
  1880.  
  1881.    push     ax
  1882.    mov      ah, 35h                       ; find address of handler
  1883.    int      DOS                           ; returned in ES:BX
  1884.    pop      ax
  1885.    ret
  1886. ENDP getvect
  1887.  
  1888.  
  1889. ;--------------------------------------------------------------------------;
  1890. ;  Purpose:    Sets an interrupt vector.
  1891. ;  Notes:      none
  1892. ;  Requires:   8086-class CPU and DOS v1.0 or better.
  1893. ;  Entry:      AL = interrupt of interest,
  1894. ;              DS:DX = address of new interrupt handler
  1895. ;  Exit:       n/a
  1896. ;  Calls:      none
  1897. ;  Changes:    none
  1898. ;--------------------------------------------------------------------------;
  1899. PROC setvect
  1900.  
  1901.    push     ax
  1902.    mov      ah, 25h                       ; set address of handler
  1903.    int      DOS
  1904.    pop      ax
  1905.    ret
  1906. ENDP setvect
  1907.  
  1908.  
  1909. EVEN
  1910. ;--------------------------------------------------------------------------;
  1911. ;  Purpose:    Finds the next in a chain of ISRs.
  1912. ;  Notes:      ISRs must be shared according to the IBM Interrupt
  1913. ;                 Sharing Protocol.
  1914. ;  Requires:   8086-class CPU.
  1915. ;  Entry:      ES:BX = entry point for a given ISR.
  1916. ;  Exit:       ES:BX = entry point for next ISR in the chain,
  1917. ;              cf = 1 on error
  1918. ;  Calls:      none
  1919. ;  Changes:    BX, ES, cf
  1920. ;--------------------------------------------------------------------------;
  1921. PROC find_NextISR
  1922.  
  1923. ; Save DS, then set it to ES. This will avoid segment overrides below.
  1924.    push     ds es
  1925.    pop      ds
  1926.  
  1927. ; Run three tests to see if the ISR obeys the protocol.
  1928. ;1) Entry should be a short jump (opcode 0EBh).
  1929. ;2) Sig should equal a special value ("KB").
  1930. ;3) Reset should be another short jump.
  1931.    cmp      [BYTE PTR (ISR PTR bx).Entry], 0ebh
  1932.    jnz      SHORT @@Abort
  1933.    cmp      [(ISR PTR bx).Sig], TSRMAGIC
  1934.    jnz      SHORT @@Abort
  1935.    cmp      [BYTE PTR (ISR PTR bx).Reset], 0ebh
  1936.    jnz      SHORT @@Abort
  1937.  
  1938. ; Ok, looks like the ISR is following the Interrupt Sharing Protocol.
  1939. ; nb: cf will be clear as a result of the last comparison.
  1940.    les      bx, [(ISR PTR bx).OldISR]
  1941.    jmp      SHORT @@Fin
  1942.  
  1943. ; Uh, oh, somebody's not being very cooperative or we've hit DOS/BIOS.
  1944. @@Abort:
  1945.    stc                                    ; flag error
  1946.  
  1947. @@Fin:
  1948.    pop      ds
  1949.    ret
  1950.  
  1951. ENDP find_NextISR
  1952.  
  1953.  
  1954. ;--------------------------------------------------------------------------;
  1955. ;  Purpose:    Finds the previous in a chain of ISRs.
  1956. ;  Notes:      ISRs must be shared according to the IBM Interrupt
  1957. ;                 Sharing Protocol.
  1958. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  1959. ;  Entry:      AL = vector hooked,
  1960. ;              ES:BX = entry point for a given ISR.
  1961. ;  Exit:       ES:BX = entry point for next ISR in the chain,
  1962. ;              cf = 1 on error
  1963. ;  Calls:      getvect, find_NextISR
  1964. ;  Changes:    BX, ES, cf
  1965. ;--------------------------------------------------------------------------;
  1966. PROC find_PrevISR
  1967.  
  1968.    push     ax cx dx
  1969.  
  1970. ; Stack holds previous ISR. Initialize it to a null pointer.
  1971.    ZERO     cx
  1972.    push     cx cx
  1973.  
  1974. ; Point CX:DX to current ISR, then get first ISR in the chain.
  1975.    mov      cx, es
  1976.    mov      dx, bx
  1977.    call     getvect                       ; AL -> ES:BX
  1978.    jmp      SHORT @@Cmp
  1979.  
  1980. ; Cycle through ISRs until either a match is found or we can't go further.
  1981. @@Next:
  1982.    add      sp, 4                         ; get rid of two words on stack
  1983.    push     es bx                         ; now save ES:BX
  1984.    call     find_NextISR                  ; ES:BX -> ES:BX
  1985.    jc       SHORT @@Fin                   ; abort on error
  1986. @@Cmp:
  1987.    mov      ax, es                        ; are segs the same?
  1988.    cmp      ax, cx
  1989.    jnz      SHORT @@Next
  1990.    cmp      dx, bx                        ; what about offsets?
  1991.    jnz      SHORT @@Next
  1992.  
  1993. @@Fin:
  1994.    pop      bx es                         ; pointer to previous ISR
  1995.    pop      dx cx ax
  1996.    ret
  1997.  
  1998. ENDP find_PrevISR
  1999.  
  2000.  
  2001. ;--------------------------------------------------------------------------;
  2002. ;  Purpose:    Hooks into an ISR and keeps track of previous ISR.
  2003. ;  Notes:      none
  2004. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  2005. ;  Entry:      AL = vector to hook,
  2006. ;              ES:BX = pointer to a structure of type ISR.
  2007. ;  Exit:       n/a
  2008. ;  Calls:      getvect, setvect
  2009. ;  Changes:    n/a
  2010. ;--------------------------------------------------------------------------;
  2011. PROC hook_ISR
  2012.  
  2013.    push     bx dx bp ds es
  2014.  
  2015. ; Save old vector to it can be restored later. Then set new hook.
  2016.    push     es bx                         ; need them later
  2017.    call     getvect                       ; AL -> ES:BX
  2018.    pop      dx ds                         ; recover pointer to ISR
  2019.    mov      bp, dx                        ; use BP for indexing
  2020.    mov      [WORD (ISR PTR bp).OldISR], bx
  2021.    mov      [WORD ((ISR PTR bp).OldISR)+2], es
  2022.    call     setvect                       ; uses DS:DX
  2023.  
  2024.    pop      es ds bp dx bx
  2025.    ret
  2026.  
  2027. ENDP hook_ISR
  2028.  
  2029.  
  2030. ;--------------------------------------------------------------------------;
  2031. ;  Purpose:    Unhooks an ISR if possible.
  2032. ;  Notes:      Unhooking an ISR is more complicated than hooking one
  2033. ;                 because of the need to support interrupt sharing.
  2034. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  2035. ;  Entry:      AL = vector hooked,
  2036. ;              ES:BX = entry point of current ISR.
  2037. ;  Exit:       cf = 1 on error
  2038. ;  Calls:      find_PrevISR, setvect
  2039. ;  Changes:    cf
  2040. ;--------------------------------------------------------------------------;
  2041. PROC unhook_ISR
  2042.  
  2043.    push     bx cx dx ds es
  2044.  
  2045. ; Point DS:DX to next ISR, then ES:BX to previous ISR in the chain.
  2046.    lds      dx, [(ISR PTR es:bx).OldISR]
  2047.    call     find_PrevISR                  ; ES:BX -> ES:BX
  2048.    jc       SHORT @@Fin                   ; abort on error
  2049.  
  2050. ; If find_PrevISR() returned a null pointer, then the current ISR
  2051. ; is first in the chain; just use DOS to reassign the vector.
  2052. ; Otherwise, update the OldISR entry in the previous handler.
  2053.    mov      cx, es                        ; did find_PrevISR() ...
  2054.    or       cx, bx                        ; return null pointer?
  2055.    jnz      SHORT @@Update                ;   no. update OldISR
  2056.    call     setvect                       ;   yes, hook AL to DS:DX
  2057.    jmp      SHORT @@Fin
  2058. @@Update:
  2059.    mov      [WORD (ISR PTR es:bx).OldISR], dx
  2060.    mov      [WORD ((ISR PTR es:bx).OldISR)+2], ds
  2061.  
  2062. @@Fin:
  2063.    pop      es ds dx cx bx
  2064.    ret
  2065.  
  2066. ENDP unhook_ISR
  2067.  
  2068.  
  2069. EVEN
  2070. AMI         equ      2dh                  ; Alternate Multiplex Interrupt
  2071. ENVBLK      equ      2ch                  ; ptr in PSP to environment block
  2072.  
  2073.  
  2074. ;--------------------------------------------------------------------------;
  2075. ;  Purpose:    Frees up a program's environment block.
  2076. ;  Notes:      Programs such as PMAP or MEM scan environment blocks to
  2077. ;                 learn names of TSRs. Freeing it means such programs
  2078. ;                 will not be able to identify the TSR.
  2079. ;              It's ASSUMED the ENV BLOCK has NOT ALREADY been FREED.
  2080. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  2081. ;  Entry:      ES = segment of program's PSP.
  2082. ;  Exit:       none
  2083. ;  Calls:      none
  2084. ;  Changes:    none
  2085. ;--------------------------------------------------------------------------;
  2086. PROC free_Env
  2087.  
  2088.    push     ax ds es
  2089.  
  2090.    push     es                            ; point DS to PSP too
  2091.    pop      ds
  2092.    mov      es, [ENVBLK]                  ; pointer to env block
  2093.    mov      ah, 49h                       ; free memory block
  2094.    int      DOS
  2095.    mov      [WORD PTR ENVBLK], 0          ; make it 0
  2096.  
  2097.    pop      es ds ax
  2098.    ret
  2099.  
  2100. ENDP free_Env
  2101.  
  2102.  
  2103. ;--------------------------------------------------------------------------;
  2104. ;  Purpose:    Replaces a program's real environment with a smaller, fake
  2105. ;                 one to save space. Programs like PMAP and MEM though
  2106. ;                 will still be able to identify TSRs.
  2107. ;  Notes:      If run with DOS version lower than v3.10, the environment
  2108. ;                 block is merely freed.
  2109. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  2110. ;  Entry:      CX = size in bytes of pseudo-environment block (if 0 
  2111. ;                 one is created containing just program name/args),
  2112. ;              DS:SI = pointer to pseudo-environment block,
  2113. ;              ES = segment of program's PSP.
  2114. ;  Exit:       none
  2115. ;  Calls:      getvdos, free_Env, strlen
  2116. ;  Changes:    none
  2117. ;--------------------------------------------------------------------------;
  2118. PROC fake_Env
  2119.  
  2120.    push     ax bx cx di si ds es
  2121.    pushf
  2122.  
  2123. ; Make sure DOS is v3.10 or better. If not, just free environment.
  2124. ; nb: I could code this to handle old versions so long as caller
  2125. ; supplies a real block, but why bother?
  2126.    call     getvdos                       ; get DOS version
  2127.    xchg     al, ah
  2128.    cmp      ax, (3 SHL 8) + 10            ; v3.10 or better?
  2129.    jae      SHORT @@FindEnv               ;   yes
  2130.    call     free_Env                      ;   no, just free it
  2131.    jmp      SHORT @@Fin
  2132.  
  2133. ; Locate environment block.
  2134. @@FindEnv:
  2135.    mov      bx, [es:ENVBLK]               ; pointer to env block
  2136.    mov      es, bx
  2137.  
  2138. ; If CX is zero, point DS:SI to just the program name/args in the
  2139. ; current environment. This format was introducted with DOS v3.10.
  2140. ;
  2141. ; nb: Refer to _Undocumented DOS, p 399 for format of environment block.
  2142.    cld                                    ; scasb and movsb must go forward
  2143.    or       cx, cx                        ; is CX zero?
  2144.    jnz      SHORT @@GetMem                ;   no
  2145.    mov      ds, bx                        ; point DS to env block too
  2146.    ZERO     al                            ; ends of ASCIIz strings
  2147.    ZERO     di                            ; start at offset 0
  2148. @@NextString:
  2149.    call     strlen                        ; find length of string at ES:DI
  2150.    add      di, cx                        ; update DI
  2151.    inc      di                            ;   and past EOS
  2152.    scasb                                  ; are we at another 0?
  2153.    jne      SHORT @@NextString            ;   no
  2154.    mov      si, di                        ; point SI to
  2155.    dec      si                            ;   EOS in ...
  2156.    dec      si                            ;   last string
  2157.    mov      [WORD PTR es:di], 1           ; only want prog name/args
  2158.    inc      di                            ; point to start of string
  2159.    inc      di
  2160.    call     strlen                        ; find its length
  2161.    add      cx, di                        ; get # bytes to move
  2162.    sub      cx, si
  2163.    inc      cx
  2164.  
  2165. ; At this point, CX holds number of bytes to allocate and DS:SI point
  2166. ; to a copy of the pseudo-environment block.
  2167. @@GetMem:
  2168.    ZERO     di                            ; either way, destination = 0
  2169.    mov      bx, cx                        ; from # bytes
  2170.    REPT     4
  2171.       shr      bx, 1                      ; get # paragraphs
  2172.    ENDM
  2173.    inc      bx                            ; think what if CX < 0fh
  2174.    push     bx                            ; must save BX if DOS fails
  2175.    mov      ah, 48h                       ; allocate memory
  2176.    int      DOS                           ; returns block in AX
  2177.    pop      bx
  2178.    jc       SHORT @@JustResize            ; cf => failure
  2179.  
  2180. ; Memory allocation succeeded so: (1) Copy to new block. (2) Adjust
  2181. ; pointer in program's PSP. (3) Free old block.
  2182.    push     es                            ; points to old env block
  2183.    mov      es, ax                        ; new block
  2184.    rep      movsb
  2185.    mov      ah, 62h                       ; get program's PSP
  2186.    int      DOS                           ; returns it in BX
  2187.    mov      ds, bx
  2188.    mov      [ENVBLK], es                  ; pointer to new env block
  2189.    pop      es                            ; recover pointer to old env
  2190.    mov      ah, 49h                       ; free it
  2191.    int      DOS
  2192.    jmp      SHORT @@Fin
  2193.  
  2194. ; Memory allocation failed so we'll use existing block and resize it.
  2195. @@JustResize:
  2196.    rep      movsb
  2197.    mov      ah, 4ah                       ; modify allocation
  2198.    int      DOS
  2199.  
  2200. @@Fin:
  2201.    popf
  2202.    pop      es ds si di cx bx ax
  2203.    ret
  2204.  
  2205. ENDP fake_Env
  2206.  
  2207.  
  2208. ;--------------------------------------------------------------------------;
  2209. ;  Purpose:    Checks if a TSR has been installed in memory.
  2210. ;  Notes:      For a description of the steps followed here, see Ralf
  2211. ;                 Brown's alternate multiplex proposal.
  2212. ;              This procedure MUST BE RUN before going resident and
  2213. ;                 the multiplex id returned SHOULD BE SAVED in the
  2214. ;                 resident data area.
  2215. ;  Requires:   8086-class CPU
  2216. ;  Entry:      DS:SI = pointer to TSR's signature string.
  2217. ;  Exit:       AL = 0 if not installed, = 1 if installed, = 2 if all
  2218. ;                 multiplex ids are in use,
  2219. ;              AH = multiplex id to use based on AL,
  2220. ;              CX = TSR version number if installed,
  2221. ;              DX:DI = pointer to resident copy of TSR's sig if AL = 1.
  2222. ;  Calls:      getvect, memcmp
  2223. ;  Changes:    AX, CX, DX, DI
  2224. ;--------------------------------------------------------------------------;
  2225. PROC check_ifInstalled
  2226.  
  2227.    push     bx es
  2228.  
  2229. ; Do a quick check to see if 2d is hooked. 
  2230.    mov      al, AMI                       ; alternate multiplex interrupt
  2231.    call     getvect                       ; handler address in ES:BX
  2232.    ZERO     ax
  2233.    cmp      [BYTE PTR es:bx], 0cfh        ; is it IRET opcode?
  2234.    jz       SHORT @@Fin                   ;   yes, return with AX = 0
  2235.  
  2236. ; Do an install check on each possible multiplex id. 
  2237.    ZERO     bx                            ; marks 1st unused mplex id
  2238. @@CheckIt:
  2239.    ZERO     al                            ; be sure to do install check
  2240.    int      AMI                           ; might trash CX and DX:DI
  2241.    or       al, al                        ; is AL zero still?
  2242.    jnz      SHORT @@CmpSigs               ;   no, multiplex's in use
  2243.  
  2244. ; It's not in use. Save if it's the first.
  2245.    or       bl, bl                        ; 1st available id found already?
  2246.    jnz      SHORT @@NextMPlex             ;   yes
  2247.    inc      bl                            ;   no, but flag it now
  2248.    mov      bh, ah                        ;     and hold onto mplex
  2249.    jmp      SHORT @@NextMPlex
  2250.  
  2251. ; Compare first 16 bytes of sigs. DS:SI points to a known sig;
  2252. ; DX:DI to one somewhere in resident code.
  2253. @@CmpSigs:
  2254.    push     cx                            ; save TSR version number
  2255.    mov      cx, 16                        ; # bytes in sigs to compare
  2256.    mov      es, dx                        ; memcmp() needs ES:DI and DS:SI
  2257.    call     memcmp
  2258.    pop      cx                            ; recover TSR version number
  2259.    jnz      SHORT @@NextMPlex
  2260.    mov      al, 1
  2261.    jmp      SHORT @@Fin
  2262.  
  2263. ; Move on to next multiplex number.
  2264. @@NextMPlex:
  2265.    add      ah, 1                         ; sets zf if AH was 255. Done?
  2266.    jnz      SHORT @@CheckIt               ;   no, back for more
  2267.    mov      ah, bh                        ;   yes, AH = 1st available id
  2268.    or       dl, bl                        ;   did we run out?
  2269.    jnz      SHORT @@Fin                   ;     no
  2270.    mov      al, 2                         ;     yes
  2271.  
  2272. @@Fin:
  2273.    pop      es bx
  2274.    ret
  2275.  
  2276. ENDP check_ifInstalled
  2277.  
  2278.  
  2279. ;--------------------------------------------------------------------------;
  2280. ;  Purpose:    Installs a TSR in memory.
  2281. ;  Notes:      This procedure never returns.
  2282. ;              No changes are made here to the environment block.
  2283. ;              Entry points are assumed relative to ES.
  2284. ;              Call check_ifInstalled() to determine which multiplex
  2285. ;                 id will be used.
  2286. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  2287. ;  Entry:      DX = number of paragraphs to reserve,
  2288. ;              ES:BX = pointer to a structure of ISRHOOK.
  2289. ;  Exit:       n/a
  2290. ;  Calls:      hook_ISR
  2291. ;  Changes:    n/a
  2292. ;--------------------------------------------------------------------------;
  2293. PROC install_TSR
  2294.  
  2295. ; Set hooks as specified by ISRHOOK structure. 
  2296.    mov      bp, bx                        ; BX needed when hooking ISRs
  2297. @@NextHook:
  2298.    mov      al, [(ISRHOOK PTR bp).Vector]
  2299.    mov      bx, [(ISRHOOK PTR bp).Entry]
  2300.    call     hook_ISR                      ; AL, ES:BX -> n/a
  2301.    add      bp, SIZE ISRHOOK
  2302.    cmp      al, AMI                       ; at end of table?
  2303.    jnz      SHORT @@NextHook              ;   no
  2304.  
  2305. ; And now go resident. Note that DX already holds # paragraphs to keep.
  2306.    mov      ax, 3100h                     ; terminate/stay resident, rc = 0
  2307.    int      DOS                           ; via DOS
  2308.    ret                                    ; ***never reached***
  2309.  
  2310. ENDP install_TSR
  2311.  
  2312.  
  2313. ;--------------------------------------------------------------------------;
  2314. ;  Purpose:    Removes a TSR if possible.
  2315. ;  Notes:      Caller should use check_ifInstalled() to make sure the
  2316. ;                 TSR has first been installed.
  2317. ;              Entry points are assumed to be relative to ES.
  2318. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  2319. ;  Entry:      ES:BX = pointer to a structure of ISRHOOK.
  2320. ;  Exit:       cf set if operation failed
  2321. ;  Calls:      find_PrevISR, unhook_ISR
  2322. ;  Changes:    AX, cf
  2323. ;--------------------------------------------------------------------------;
  2324. PROC remove_TSR
  2325.  
  2326.    push     bx dx bp ds es                ; save registers
  2327.  
  2328. ; Set DS to ES to avoid segment overrides. Also, use BP for indexing into 
  2329. ; the hook table, and save it in DX as it's needed later.
  2330.    push     es
  2331.    pop      ds
  2332.    mov      bp, bx
  2333.    mov      dx, bx
  2334.  
  2335. ; For each vector in the hook table, make sure the ISR can be unhooked.
  2336. @@NextVect:
  2337.    mov      al, [(ISRHOOK PTR bp).Vector]
  2338.    mov      bx, [(ISRHOOK PTR bp).Entry]
  2339.    push     es                            ; hang onto this
  2340.    call     find_PrevISR                  ; able to find it?
  2341.    pop      es
  2342.    jc       SHORT @@Fin                   ;   no, abort
  2343.    add      bp, SIZE ISRHOOK
  2344.    cmp      al, AMI                       ; at end of table?
  2345.    jnz      SHORT @@NextVect              ;   no
  2346.  
  2347. ; It's possible to unhook all vectors, so go to it. 
  2348.    mov      bp, dx
  2349. @@NextHook:
  2350.    mov      al, [(ISRHOOK PTR bp).Vector]
  2351.    mov      bx, [(ISRHOOK PTR bp).Entry]
  2352.    call     unhook_ISR                    ; AL, ES:BX -> n/a
  2353.    jc       SHORT @@Fin                   ; it had better succeed!
  2354.    add      bp, SIZE ISRHOOK
  2355.    cmp      al, AMI                       ; at end of table?
  2356.    jnz      SHORT @@NextHook              ;   no
  2357.  
  2358. ; Now free TSR's memory.
  2359.    mov      bx, [ENVBLK]
  2360.    or       bx, bx                        ; any environment block?
  2361.    jz       SHORT @@MainMem               ;   no
  2362.    mov      es, bx                        ;   yes, free it
  2363.    mov      ah, 49h
  2364.    int      DOS                           ; trashes AH
  2365.    jc       SHORT @@Fin                   ; shouldn't be necessary
  2366. @@MainMem:
  2367.    mov      ah, 49h
  2368.    mov      bx, ds                        ; free TSR's memory
  2369.    mov      es, bx
  2370.    int      DOS
  2371.  
  2372. @@Fin:
  2373.    pop      es ds bp dx bx                ; pop registers
  2374.    ret
  2375.  
  2376. ENDP remove_TSR
  2377.  
  2378.  
  2379. EVEN
  2380. ;-------------------------------------------------------------------------;
  2381. ;  Purpose:    Converts character to lowercase.
  2382. ;  Notes:      none
  2383. ;  Requires:   8086-class CPU.
  2384. ;  Entry:      AL = character to be converted.
  2385. ;  Exit:       AL = converted character.
  2386. ;  Calls:      none
  2387. ;  Changes:    AL
  2388. ;              flags
  2389. ;-------------------------------------------------------------------------;
  2390. PROC tolower
  2391.  
  2392.    cmp      al, 'A'                       ; if < 'A' then done
  2393.    jb       SHORT @@Fin
  2394.    cmp      al, 'Z'                       ; if > 'Z' then done
  2395.    ja       SHORT @@Fin
  2396.    or       al, 20h                       ; make it lowercase
  2397. @@Fin:
  2398.    ret
  2399.  
  2400. ENDP tolower
  2401.  
  2402.  
  2403. ;-------------------------------------------------------------------------;
  2404. ;  Purpose:    Converts character to uppercase.
  2405. ;  Notes:      none
  2406. ;  Requires:   8086-class CPU.
  2407. ;  Entry:      AL = character to be converted.
  2408. ;  Exit:       AL = converted character.
  2409. ;  Calls:      none
  2410. ;  Changes:    AL
  2411. ;              flags
  2412. ;-------------------------------------------------------------------------;
  2413. PROC toupper
  2414.  
  2415.    cmp      al, 'a'                       ; if < 'a' then done
  2416.    jb       SHORT @@Fin
  2417.    cmp      al, 'z'                       ; if > 'z' then done
  2418.    ja       SHORT @@Fin
  2419.    and      al, not 20h                   ; make it lowercase
  2420. @@Fin:
  2421.    ret
  2422.  
  2423. ENDP toupper
  2424.  
  2425.  
  2426. EVEN
  2427. ;--------------------------------------------------------------------------;
  2428. ;  Purpose:    Compares two regions of memory.
  2429. ;  Notes:      none
  2430. ;  Requires:   8086-class CPU.
  2431. ;  Entry:      CX = number of bytes to compare,
  2432. ;              DS:SI = start of 1st region of memory,
  2433. ;              ES:DI = start of 2nd region.
  2434. ;  Exit:       zf = 1 if equal.
  2435. ;  Calls:      none
  2436. ;  Changes:    zf
  2437. ;--------------------------------------------------------------------------;
  2438. PROC memcmp
  2439.  
  2440.    push     cx di si
  2441.    pushf                                  ; save direction flag
  2442.    cld
  2443.    repe     cmpsb                         ; compare both areas
  2444.    popf                                   ; recover direction flag
  2445.    dec      di
  2446.    dec      si
  2447.    cmpsb                                  ; set flags based on final byte
  2448.    pop      si di cx
  2449.    ret
  2450.  
  2451. ENDP memcmp
  2452.  
  2453.  
  2454. EVEN
  2455. ;-------------------------------------------------------------------------;
  2456. ;  Purpose:    Calculates length of an ASCIIZ string.
  2457. ;  Notes:      Terminal char is _not_ included in the count.
  2458. ;  Requires:   8086-class CPU.
  2459. ;  Entry:      ES:DI = pointer to string.
  2460. ;  Exit:       CX = length of string,
  2461. ;              cf = 0 and zf = 1 if EOS found,
  2462. ;              cf = 1 and zf = 0 if EOS not found within segment.
  2463. ;  Calls:      none
  2464. ;  Changes:    CX,
  2465. ;              flags
  2466. ;-------------------------------------------------------------------------;
  2467. PROC strlen
  2468.  
  2469.    push     ax di
  2470.    pushf
  2471.    cld                                    ; scan forward only
  2472.    mov      al, EOS                       ; character to search for
  2473.    mov      cx, di                        ; where are we now
  2474.    not      cx                            ; what's left in segment - 1
  2475.    push     cx                            ; save char count
  2476.    repne    scasb
  2477.    je       SHORT @@Done
  2478.    scasb                                  ; test final char
  2479.    dec      cx                            ; avoids trouble with "not" below
  2480.  
  2481. @@Done:
  2482.    pop      ax                            ; get original count
  2483.    sub      cx, ax                        ; subtract current count
  2484.    not      cx                            ; and invert it
  2485.    popf                                   ; restore df
  2486.    dec      di
  2487.    cmp      [BYTE PTR es:di], EOS
  2488.    je       SHORT @@Fin                   ; cf = 0 if equal
  2489.    stc                                    ; set cf => error
  2490.  
  2491. @@Fin:
  2492.    pop      di ax
  2493.    ret
  2494.  
  2495. ENDP strlen
  2496.  
  2497.  
  2498. END
  2499.